security: payment-URI amount validation, export filename sanitize, chat crash-guard, HTTPS-only, conf 0600, pool auto-lock
Six bounded defensive fixes: - Payment-URI amount is now strictly validated (reject NaN/inf/sign/garbage, <=8 decimals, 0..21,000,000) at parse time, so a crafted QR/URI can't inject a bogus amount into the Send form. The z_sendmany call the user confirms is unchanged; this is input validation only. - export-all-keys filename is sanitized (basename-only, no traversal) via a shared util::sanitizeExportFilename (factored out of the CSV export dialog). - Chat append()/upsert() wrap the strict serialize in try/catch so a malformed (invalid-UTF-8, attacker-influenceable) body is skipped, not crashed on — matching appendBatch's existing drop behavior. - http_download (used by the updaters + pool stats) restricts transfers AND redirects to HTTPS and bounds the download path's redirects, so a redirect can't downgrade to http:// or another scheme. - The default DRGX.conf (rpcuser/rpcpassword) is written 0600 from the start via writeFileAtomically, closing the brief world-readable ofstream+chmod window. - Auto-lock is suppressed only for SOLO mining (needs the wallet), not pool mining (pays an external address), which was holding the wallet open forever. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#include "../../rpc/rpc_worker.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../../util/platform.h"
|
||||
#include "../../util/export_filename.h"
|
||||
#include "../notifications.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
@@ -160,8 +161,11 @@ void ExportAllKeysDialog::render(App* app)
|
||||
if (s_include_t) {
|
||||
for (const auto& a : state.t_addresses) t_addrs.push_back(a.address);
|
||||
}
|
||||
std::string filename(s_filename);
|
||||
|
||||
// Sanitize the typed filename (basename only, no traversal) and reflect it back in the
|
||||
// buffer so the user sees exactly what gets written. Mirrors the CSV export dialog.
|
||||
std::string filename = util::sanitizeExportFilename(s_filename, ".txt", "dragonx_keys.txt");
|
||||
snprintf(s_filename, sizeof(s_filename), "%s", filename.c_str());
|
||||
|
||||
// Run all key exports on worker thread
|
||||
if (app->worker()) {
|
||||
app->worker()->post([rpc = app->rpc(), z_addrs, t_addrs, filename]() -> rpc::RPCWorker::MainCb {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "../../app.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../../util/platform.h"
|
||||
#include "../../util/export_filename.h"
|
||||
#include "../../data/transaction_csv.h"
|
||||
#include "transactions_tab.h"
|
||||
#include "../notifications.h"
|
||||
@@ -49,31 +50,6 @@ static bool parseYmd(const char* s, std::int64_t& outStartOfDay) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Make a user-typed filename safe to write into the config dir: basename only, illegal chars → '_',
|
||||
// no leading dots / trailing dots-or-spaces, length-capped, always ending in .csv.
|
||||
static std::string sanitizeExportFilename(std::string name) {
|
||||
const size_t slash = name.find_last_of("/\\");
|
||||
if (slash != std::string::npos) name = name.substr(slash + 1);
|
||||
auto notspace = [](unsigned char c) { return !std::isspace(c); };
|
||||
name.erase(name.begin(), std::find_if(name.begin(), name.end(), notspace));
|
||||
name.erase(std::find_if(name.rbegin(), name.rend(), notspace).base(), name.end());
|
||||
for (char& c : name) {
|
||||
const unsigned char u = static_cast<unsigned char>(c);
|
||||
if (u < 0x20 || c == '<' || c == '>' || c == ':' || c == '"' || c == '|' || c == '?' || c == '*')
|
||||
c = '_';
|
||||
}
|
||||
const size_t firstGood = name.find_first_not_of('.');
|
||||
if (firstGood == std::string::npos) name.clear(); else name.erase(0, firstGood);
|
||||
while (!name.empty() && (name.back() == '.' || name.back() == ' ')) name.pop_back();
|
||||
if (name.size() > 120) name.resize(120);
|
||||
std::string lower = name;
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
if (lower.size() < 4 || lower.compare(lower.size() - 4, 4, ".csv") != 0) name += ".csv";
|
||||
if (name.empty() || name == ".csv") name = "dragonx_transactions.csv";
|
||||
return name;
|
||||
}
|
||||
|
||||
void ExportTransactionsDialog::show()
|
||||
{
|
||||
s_open = true;
|
||||
@@ -226,7 +202,7 @@ void ExportTransactionsDialog::render(App* app)
|
||||
std::string configDir = util::Platform::getConfigDir();
|
||||
// Sanitize in-buffer so the user sees exactly what was written (and we can't be pointed outside
|
||||
// the config dir by a pasted path).
|
||||
std::string safeName = sanitizeExportFilename(s_filename);
|
||||
std::string safeName = util::sanitizeExportFilename(s_filename, ".csv", "dragonx_transactions.csv");
|
||||
snprintf(s_filename, sizeof(s_filename), "%s", safeName.c_str());
|
||||
std::string filepath = configDir + "/" + safeName;
|
||||
// Build the chosen subset (scope 0 exports the whole list with no copy), then write atomically
|
||||
|
||||
Reference in New Issue
Block a user