Security-Fixes aus dem flutter-security Audit

CRIT:
- Cloud-Token jetzt in flutter_secure_storage (Keystore/Keychain) statt SharedPreferences, mit Migration alter Eintraege
- usesCleartextTraffic entfernt (kein HTTP-Klartext mehr)
HIGH:
- Path-Traversal gefixt: p.basename bei YouTube-Download, Navidrome-Download und Cloud-Auto-Sync
MED:
- allowBackup=false (kein Backup von Token-Daten)
- Navidrome-Salt kryptographisch sicher (Random.secure statt Timestamp)
This commit is contained in:
Hermes (Server)
2026-08-01 13:10:18 +02:00
parent 8b6a04ead8
commit 77a20422be
5 changed files with 45 additions and 17 deletions
+28 -8
View File
@@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../config/app_config.dart';
import '../services/melo_logger.dart';
@@ -19,6 +20,10 @@ class CloudService {
String _user = '';
String _token = '';
/// Token liegt verschlüsselt im Keychain/Keystore (flutter_secure_storage) —
/// nicht mehr im Klartext in SharedPreferences (Security-Audit CRIT-1).
static const _secure = FlutterSecureStorage();
String get user => _user;
String get token => _token;
bool get istAngemeldet => _token.isNotEmpty;
@@ -48,12 +53,29 @@ class CloudService {
}
/// Stellt gespeicherten Token wieder her (Auto-Login nach App-Start).
/// Migriert einmalig alte SharedPreferences-Einträge in SecureStorage.
Future<bool> restoreLogin() async {
final prefs = await SharedPreferences.getInstance();
final t = prefs.getString('melo_cloud_token') ?? '';
var t = await _secure.read(key: 'melo_cloud_token') ?? '';
var u = await _secure.read(key: 'melo_cloud_user') ?? '';
// Migration alter Versionen (Token lag früher in SharedPreferences)
if (t.isEmpty) {
final altT = prefs.getString('melo_cloud_token') ?? '';
final altU = prefs.getString('melo_cloud_user') ?? '';
if (altT.isNotEmpty) {
t = altT;
u = altU;
await _secure.write(key: 'melo_cloud_token', value: t);
if (u.isNotEmpty) {
await _secure.write(key: 'melo_cloud_user', value: u);
}
await prefs.remove('melo_cloud_token');
await prefs.remove('melo_cloud_user');
}
}
if (t.isEmpty) return false;
_token = t;
_user = prefs.getString('melo_cloud_user') ?? '';
_user = u;
MeloLogger.cloudToken = t;
return true;
}
@@ -62,15 +84,13 @@ class CloudService {
_token = '';
_user = '';
MeloLogger.cloudToken = null;
final prefs = await SharedPreferences.getInstance();
await prefs.remove('melo_cloud_token');
await prefs.remove('melo_cloud_user');
await _secure.delete(key: 'melo_cloud_token');
await _secure.delete(key: 'melo_cloud_user');
}
Future<void> _speichereToken() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('melo_cloud_token', _token);
await prefs.setString('melo_cloud_user', _user);
await _secure.write(key: 'melo_cloud_token', value: _token);
await _secure.write(key: 'melo_cloud_user', value: _user);
}
Map<String, String> get _authHeader => {