diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..76c90b4 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2026-07-06 - Prevent Information Leakage in RPC Errors +**Vulnerability:** Internal RPC error messages (e.g. from bitcoind-rpc) were being exposed directly to users via `message.reply(err.message)` when a transaction failed. This can leak sensitive internal information (like network details, specific failure reasons, or connection paths). +**Learning:** Error messages from internal components like RPC daemon wrappers often contain details not intended for users. The bot needs to handle these errors internally (logging them for debug purposes) and return a generic error message to the user. +**Prevention:** Replaced `message.reply(err.message)` with `console.error(err); message.reply('An internal error occurred. Please try again later.')` across all tipbot modules to ensure users only see safe error messages. diff --git a/bot/modules/dogeTipper.js b/bot/modules/dogeTipper.js index 54258df..805857f 100644 --- a/bot/modules/dogeTipper.js +++ b/bot/modules/dogeTipper.js @@ -158,7 +158,7 @@ function doWithdraw(message, tipper, words, helpmsg) { } doge.sendFrom(tipper, address, Number(amount), function (err, txId) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { message.channel.send({ embed: { @@ -263,7 +263,7 @@ function doTip(bot, message, tipper, words, helpmsg) { function sendDOGE(bot, message, tipper, recipient, amount, privacyFlag) { getAddress(recipient.toString(), function (err, address) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { doge.sendFrom( tipper, @@ -274,7 +274,7 @@ function sendDOGE(bot, message, tipper, recipient, amount, privacyFlag) { null, function (err, txId) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { if (privacyFlag) { let userProfile = message.guild.members.get(recipient); // ⚡ Bolt: O(1) direct ID lookup vs O(N) linear search; diff --git a/bot/modules/exampleTipper.js b/bot/modules/exampleTipper.js index 25be559..979c89b 100644 --- a/bot/modules/exampleTipper.js +++ b/bot/modules/exampleTipper.js @@ -143,7 +143,7 @@ function doWithdraw(message, tipper, words, helpmsg) { } ltc.sendFrom(tipper, address, Number(amount), function(err, txId) { if (err) { - message.reply(err.message).then(message => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then(message => message.delete(10000)); } else { message.channel.send({embed:{ title: '**:outbox_tray::money_with_wings::moneybag:Litecoin (LTC) Transaction Completed!:moneybag::money_with_wings::outbox_tray:**', @@ -228,11 +228,11 @@ function doTip(bot, message, tipper, words, helpmsg) { function sendLTC(bot, message, tipper, recipient, amount, privacyFlag) { getAddress(recipient.toString(), function(err, address) { if (err) { - message.reply(err.message).then(message => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then(message => message.delete(10000)); } else { ltc.sendFrom(tipper, address, Number(amount), 1, null, null, function(err, txId) { if (err) { - message.reply(err.message).then(message => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then(message => message.delete(10000)); } else { if (privacyFlag) { let userProfile = message.guild.members.get(recipient) // ⚡ Bolt: O(1) direct ID lookup vs O(N) linear search; diff --git a/bot/modules/ftcTipper.js b/bot/modules/ftcTipper.js index 22bb0b9..6ef74c2 100644 --- a/bot/modules/ftcTipper.js +++ b/bot/modules/ftcTipper.js @@ -158,7 +158,7 @@ function doWithdraw(message, tipper, words, helpmsg) { } ftc.sendFrom(tipper, address, Number(amount), function (err, txId) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { message.channel.send({ embed: { @@ -263,7 +263,7 @@ function doTip(bot, message, tipper, words, helpmsg) { function sendFTC(bot, message, tipper, recipient, amount, privacyFlag) { getAddress(recipient.toString(), function (err, address) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { ftc.sendFrom( tipper, @@ -274,7 +274,7 @@ function sendFTC(bot, message, tipper, recipient, amount, privacyFlag) { null, function (err, txId) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { if (privacyFlag) { let userProfile = message.guild.members.get(recipient); // ⚡ Bolt: O(1) direct ID lookup vs O(N) linear search; diff --git a/bot/modules/lbcTipper.js b/bot/modules/lbcTipper.js index 09ff576..db7de90 100644 --- a/bot/modules/lbcTipper.js +++ b/bot/modules/lbcTipper.js @@ -158,7 +158,7 @@ function doWithdraw(message, tipper, words, helpmsg) { } lbc.sendFrom(tipper, address, Number(amount), function (err, txId) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { message.channel.send({ embed: { @@ -263,7 +263,7 @@ function doTip(bot, message, tipper, words, helpmsg) { function sendLBC(bot, message, tipper, recipient, amount, privacyFlag) { getAddress(recipient.toString(), function (err, address) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { lbc.sendFrom( tipper, @@ -274,7 +274,7 @@ function sendLBC(bot, message, tipper, recipient, amount, privacyFlag) { null, function (err, txId) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { if (privacyFlag) { let userProfile = message.guild.members.get(recipient); // ⚡ Bolt: O(1) direct ID lookup vs O(N) linear search; diff --git a/bot/modules/protonTipper.js b/bot/modules/protonTipper.js index 53da2cd..a88be85 100644 --- a/bot/modules/protonTipper.js +++ b/bot/modules/protonTipper.js @@ -158,7 +158,7 @@ function doWithdraw(message, tipper, words, helpmsg) { } proton.sendFrom(tipper, address, Number(amount), function (err, txId) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { message.channel.send({ embed: { @@ -263,7 +263,7 @@ function doTip(bot, message, tipper, words, helpmsg) { function sendPROTON(bot, message, tipper, recipient, amount, privacyFlag) { getAddress(recipient.toString(), function (err, address) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { proton.sendFrom( tipper, @@ -274,7 +274,7 @@ function sendPROTON(bot, message, tipper, recipient, amount, privacyFlag) { null, function (err, txId) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { if (privacyFlag) { let userProfile = message.guild.members.get(recipient); // ⚡ Bolt: O(1) direct ID lookup vs O(N) linear search; diff --git a/bot/modules/pxcTipper.js b/bot/modules/pxcTipper.js index db95dc4..7533a5b 100644 --- a/bot/modules/pxcTipper.js +++ b/bot/modules/pxcTipper.js @@ -158,7 +158,7 @@ function doWithdraw(message, tipper, words, helpmsg) { } pxc.sendFrom(tipper, address, Number(amount), function (err, txId) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { message.channel.send({ embed: { @@ -263,7 +263,7 @@ function doTip(bot, message, tipper, words, helpmsg) { function sendPXC(bot, message, tipper, recipient, amount, privacyFlag) { getAddress(recipient.toString(), function (err, address) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { pxc.sendFrom( tipper, @@ -274,7 +274,7 @@ function sendPXC(bot, message, tipper, recipient, amount, privacyFlag) { null, function (err, txId) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { if (privacyFlag) { let userProfile = message.guild.members.get(recipient); // ⚡ Bolt: O(1) direct ID lookup vs O(N) linear search; diff --git a/bot/modules/rvnTipper.js b/bot/modules/rvnTipper.js index 814abc4..79941c7 100644 --- a/bot/modules/rvnTipper.js +++ b/bot/modules/rvnTipper.js @@ -158,7 +158,7 @@ function doWithdraw(message, tipper, words, helpmsg) { } rvn.sendFrom(tipper, address, Number(amount), function (err, txId) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { message.channel.send({ embed: { @@ -263,7 +263,7 @@ function doTip(bot, message, tipper, words, helpmsg) { function sendRVN(bot, message, tipper, recipient, amount, privacyFlag) { getAddress(recipient.toString(), function (err, address) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { rvn.sendFrom( tipper, @@ -274,7 +274,7 @@ function sendRVN(bot, message, tipper, recipient, amount, privacyFlag) { null, function (err, txId) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { if (privacyFlag) { let userProfile = message.guild.members.get(recipient); // ⚡ Bolt: O(1) direct ID lookup vs O(N) linear search; diff --git a/bot/modules/ufoTipper.js b/bot/modules/ufoTipper.js index 74c1cd5..4fa6079 100644 --- a/bot/modules/ufoTipper.js +++ b/bot/modules/ufoTipper.js @@ -162,7 +162,7 @@ function doWithdraw(message, tipper, words, helpmsg) { } ufo.sendFrom(tipper, address, Number(amount), function (err, txId) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { message.channel.send({ embed: { @@ -267,7 +267,7 @@ function doTip(bot, message, tipper, words, helpmsg) { function sendUFO(bot, message, tipper, recipient, amount, privacyFlag) { getAddress(recipient.toString(), function (err, address) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { ufo.sendFrom( tipper, @@ -278,7 +278,7 @@ function sendUFO(bot, message, tipper, recipient, amount, privacyFlag) { null, function (err, txId) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { if (privacyFlag) { let userProfile = message.guild.members.get(recipient); // ⚡ Bolt: O(1) direct ID lookup vs O(N) linear search; diff --git a/bot/modules/vtlTipper.js b/bot/modules/vtlTipper.js index 040a3fa..801399f 100644 --- a/bot/modules/vtlTipper.js +++ b/bot/modules/vtlTipper.js @@ -158,7 +158,7 @@ function doWithdraw(message, tipper, words, helpmsg) { } vtl.sendFrom(tipper, address, Number(amount), function (err, txId) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { message.channel.send({ embed: { @@ -263,7 +263,7 @@ function doTip(bot, message, tipper, words, helpmsg) { function sendVTL(bot, message, tipper, recipient, amount, privacyFlag) { getAddress(recipient.toString(), function (err, address) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { vtl.sendFrom( tipper, @@ -274,7 +274,7 @@ function sendVTL(bot, message, tipper, recipient, amount, privacyFlag) { null, function (err, txId) { if (err) { - message.reply(err.message).then((message) => message.delete(10000)); + console.error(err); message.reply("An internal error occurred. Please try again later.").then((message) => message.delete(10000)); } else { if (privacyFlag) { let userProfile = message.guild.members.get(recipient); // ⚡ Bolt: O(1) direct ID lookup vs O(N) linear search;