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:
2026-10-19 11:39:38 -05:00
co-authored by Claude Opus 4.8
parent 83ce73f26b
commit d16bbf3695
9 changed files with 165 additions and 63 deletions
+30 -5
View File
@@ -10,6 +10,29 @@
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cmath>
namespace {
// Strictly parse a decimal DRGX amount from an (attacker-influenceable) payment URI / QR: digits with at
// most one '.', at most 8 fractional digits, in [0, 21000000]. Rejects empty, NaN/inf, signs, whitespace
// and trailing garbage that std::stod would otherwise accept — so a crafted URI can't inject a bogus
// amount into the Send form.
bool parseStrictAmount(const std::string& s, double& out) {
if (s.empty() || s.size() > 32) return false;
int dots = 0, digits = 0, frac = 0; bool afterDot = false;
for (char c : s) {
if (c == '.') { if (++dots > 1) return false; afterDot = true; }
else if (c >= '0' && c <= '9') { ++digits; if (afterDot && ++frac > 8) return false; }
else return false;
}
if (digits == 0) return false;
double v = 0.0;
try { v = std::stod(s); } catch (...) { return false; }
if (!std::isfinite(v) || v < 0.0 || v > 21000000.0) return false;
out = v;
return true;
}
} // namespace
namespace dragonx {
namespace util {
@@ -141,11 +164,13 @@ PaymentURI parsePaymentURI(const std::string& uri)
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
if (key == "amount" || key == "amt") {
try {
result.amount = std::stod(value);
} catch (...) {
// Invalid amount, keep as 0
double a = 0.0;
if (!parseStrictAmount(value, a)) {
result.error = "Invalid amount";
result.valid = false;
return result; // reject the whole URI rather than silently treating it as 0
}
result.amount = a;
} else if (key == "label") {
result.label = value;
} else if (key == "memo") {
@@ -174,7 +199,7 @@ PaymentURI parsePaymentURI(const std::string& uri)
}
// Validate amount if present
if (result.amount < 0) {
if (!std::isfinite(result.amount) || result.amount < 0 || result.amount > 21000000.0) {
result.error = "Invalid negative amount";
return result;
}