diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..9993dd1 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2024-05-24 - Prevent RPC Error Leakage +**Vulnerability:** Internal RPC error messages from bitcoind-rpc connections were directly passed to Discord users via `message.reply(err.message)`, potentially exposing sensitive infrastructure details. Additionally, `.catch()` handlers were missing on the subsequent `message.delete()` calls. +**Learning:** Sending raw daemon error messages to end-users is an information disclosure vulnerability. Missing `.catch()` handlers on Discord API calls can crash the bot due to unhandled promise rejections. +**Prevention:** Always log the actual error internally (e.g., `console.error(err)`) and send a generic user-friendly message. Always append `.catch()` to discord.js API calls. diff --git a/bot/modules/dogeTipper.js b/bot/modules/dogeTipper.js index 54258df..9a8db30 100644 --- a/bot/modules/dogeTipper.js +++ b/bot/modules/dogeTipper.js @@ -158,7 +158,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } else { message.channel.send({ embed: { @@ -263,7 +266,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } else { doge.sendFrom( tipper, @@ -274,7 +280,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } 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..8c8b718 100644 --- a/bot/modules/exampleTipper.js +++ b/bot/modules/exampleTipper.js @@ -143,7 +143,8 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message.reply('An error occurred while processing your request.').then(m => m.delete(10000).catch(() => {})); } else { message.channel.send({embed:{ title: '**:outbox_tray::money_with_wings::moneybag:Litecoin (LTC) Transaction Completed!:moneybag::money_with_wings::outbox_tray:**', @@ -228,11 +229,13 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message.reply('An error occurred while processing your request.').then(m => m.delete(10000).catch(() => {})); } 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); // 🛡️ Sentinel: Prevent RPC error leakage + message.reply('An error occurred while processing your request.').then(m => m.delete(10000).catch(() => {})); } 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..5c63aba 100644 --- a/bot/modules/ftcTipper.js +++ b/bot/modules/ftcTipper.js @@ -158,7 +158,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } else { message.channel.send({ embed: { @@ -263,7 +266,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } else { ftc.sendFrom( tipper, @@ -274,7 +280,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } 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..1b06ea6 100644 --- a/bot/modules/lbcTipper.js +++ b/bot/modules/lbcTipper.js @@ -158,7 +158,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } else { message.channel.send({ embed: { @@ -263,7 +266,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } else { lbc.sendFrom( tipper, @@ -274,7 +280,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } 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..9c5c845 100644 --- a/bot/modules/protonTipper.js +++ b/bot/modules/protonTipper.js @@ -158,7 +158,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } else { message.channel.send({ embed: { @@ -263,7 +266,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } else { proton.sendFrom( tipper, @@ -274,7 +280,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } 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..ab0aad0 100644 --- a/bot/modules/pxcTipper.js +++ b/bot/modules/pxcTipper.js @@ -158,7 +158,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } else { message.channel.send({ embed: { @@ -263,7 +266,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } else { pxc.sendFrom( tipper, @@ -274,7 +280,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } 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..1617fa9 100644 --- a/bot/modules/rvnTipper.js +++ b/bot/modules/rvnTipper.js @@ -158,7 +158,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } else { message.channel.send({ embed: { @@ -263,7 +266,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } else { rvn.sendFrom( tipper, @@ -274,7 +280,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } 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..49a562e 100644 --- a/bot/modules/ufoTipper.js +++ b/bot/modules/ufoTipper.js @@ -162,7 +162,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } else { message.channel.send({ embed: { @@ -267,7 +270,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } else { ufo.sendFrom( tipper, @@ -278,7 +284,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } 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..54b3a03 100644 --- a/bot/modules/vtlTipper.js +++ b/bot/modules/vtlTipper.js @@ -158,7 +158,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } else { message.channel.send({ embed: { @@ -263,7 +266,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } else { vtl.sendFrom( tipper, @@ -274,7 +280,10 @@ 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); // 🛡️ Sentinel: Prevent RPC error leakage + message + .reply('An error occurred while processing your request.') + .then((m) => m.delete(10000).catch(() => {})); } else { if (privacyFlag) { let userProfile = message.guild.members.get(recipient); // ⚡ Bolt: O(1) direct ID lookup vs O(N) linear search;