refactor(mwc): read API secret from NodeModel instead of hardcoding
What changed, and why it matters
This commit removes a hardcoded API password that was embedded in the Stack Wallet source code for connecting to a default Mimblewimblecoin (MWC) node. Previously, anyone reading the code could see the secret password. Now the wallet reads the password from user-configured node settings instead. This is a security improvement, but the change also shows that a real secret was once hardcoded and may have been exposed to anyone inspecting older versions of the app.
Treat the previously hardcoded secret as compromised: rotate it on any affected default MWC node infrastructure, verify no other hardcoded secrets remain in the codebase, and review whether the secret was ever exposed in published app binaries or source history. Consider documenting this as a security-relevant change even though the commit message does not.
Security signals we found
Hardcoded credential removed from source code
Authentication secret now sourced from user-supplied NodeModel configuration
Default node host string matching replaced by explicit secret parameter
Secret written to local wallet directory file only when configured
Commit message describes the change as a refactor, not a security fix
Evidence from the diff
The patch refactors MWC node authentication. It deletes the hardcoded default credentials (username ‘mwcmain’ and password ‘11ne3EAUtOXVKwhxm84U’) from test_mwcmqs_connection.dart and mimblewimblecoin_wallet.dart. It adds an apiSecret field to NodeFormData, passes it through the connection test, and writes it to a .api_secret file only when the user has configured one. The previous behavior matched the default node host by string and automatically used the embedded secret.
Changed components
lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dartlib/utilities/test_mwcmqs_connection.dartlib/wallets/wallet/impl/mimblewimblecoin_wallet.dartInspect captured patch +19 / −23
diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart
index 75411e9..b05815e 100644
--- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart
+++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart
@@ -751,7 +751,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
}
class NodeFormData {
- String? name, host, login, password;
+ String? name, host, login, password, apiSecret;
int? port;
bool? useSSL, isFailover, trusted, forceNoTor, isPrimary;
TorPlainNetworkOption? netOption;
diff --git a/lib/utilities/test_mwcmqs_connection.dart b/lib/utilities/test_mwcmqs_connection.dart
index c199afc..48860df 100644
--- a/lib/utilities/test_mwcmqs_connection.dart
+++ b/lib/utilities/test_mwcmqs_connection.dart
@@ -17,15 +17,13 @@ import '../services/tor_service.dart';
import 'logger.dart';
import 'prefs.dart';
-Future<bool> _testMwcMqsNodeConnection(Uri uri) async {
+Future<bool> _testMwcMqsNodeConnection(Uri uri, {String? apiSecret}) async {
final HTTP client = HTTP();
try {
final headers = {'Content-Type': 'application/json'};
- if (uri.toString() == 'https://mwc713.mwc.mw/v1/version') {
- const username = 'mwcmain';
- const password = '11ne3EAUtOXVKwhxm84U';
- final credentials = base64Encode(utf8.encode('$username:$password'));
+ if (apiSecret != null) {
+ final credentials = base64Encode(utf8.encode('mwcmain:$apiSecret'));
headers['Authorization'] = 'Basic $credentials';
}
final response = await client
@@ -80,7 +78,7 @@ Future<NodeFormData?> testMwcNodeConnection(NodeFormData data) async {
uri = uri.replace(port: data.port);
try {
- if (await _testMwcMqsNodeConnection(uri)) {
+ if (await _testMwcMqsNodeConnection(uri, apiSecret: data.apiSecret)) {
return data;
} else {
return null;
diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart
index a577b03..d703463 100644
--- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart
+++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart
@@ -569,6 +569,17 @@ class MimblewimblecoinWallet extends Bip39Wallet {
// ================= Private =================================================
+ Future<void> _ensureApiSecret(String walletDir) async {
+ final file = File('$walletDir/.api_secret');
+ final secret = _mimblewimblecoinNode?.nodeApiSecret;
+ if (secret != null) {
+ await Directory(walletDir).create(recursive: true);
+ await file.writeAsString(secret);
+ } else if (await file.exists()) {
+ await file.delete();
+ }
+ }
+
Future<String> _getConfig() async {
if (_mimblewimblecoinNode == null) {
await updateNode();
@@ -582,7 +593,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
final String nodeApiAddress = uri.toString();
final walletDir = await _currentWalletDirPath();
- await _ensureApiSecret(walletDir, nodeApiAddress);
+ await _ensureApiSecret(walletDir);
final Map<String, dynamic> config = {};
config["wallet_dir"] = walletDir;
@@ -593,20 +604,6 @@ class MimblewimblecoinWallet extends Bip39Wallet {
return stringConfig;
}
- /// Write the node API secret to .api_secret in the wallet directory so that
- /// the Rust HTTPNodeClient can authenticate to the MWC node.
- Future<void> _ensureApiSecret(String walletDir, String nodeUrl) async {
- const defaultNodeHost = 'mwc713.mwc.mw';
- const defaultNodeSecret = '11ne3EAUtOXVKwhxm84U';
-
- final file = File('$walletDir/.api_secret');
- if (nodeUrl.contains(defaultNodeHost)) {
- await Directory(walletDir).create(recursive: true);
- await file.writeAsString(defaultNodeSecret);
- } else if (await file.exists()) {
- await file.delete();
- }
- }
Future<String> _currentWalletDirPath() async {
final Directory appDir = await StackFileSystem.applicationRootDirectory();
@@ -1535,7 +1532,8 @@ class MimblewimblecoinWallet extends Bip39Wallet {
NodeFormData()
..host = node!.host
..useSSL = node.useSSL
- ..port = node.port,
+ ..port = node.port
+ ..apiSecret = node.nodeApiSecret,
) !=
null;
} catch (e, s) {
Why this scored 59/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.