Umsetzung von #567. Entscheidung Richard, 13.09.2026: Geheimnisse verwalten wir ausschliesslich im Desktop, und selbst dort erst nach zusaetzlicher Passwortabfrage. Bisher sah und aenderte jeder, der die Desktop-Oberflaeche offen vor sich hatte: - Polymarket-Konten mit ApiKey, ApiSecret, ApiPassphrase und PrivateKey - Master-Key, OpenRouter-Key, Watchdog-Token und Lizenzschluessel - Mullvad-Account und Polygon-RPC-URL mit Alchemy-Key, offen im Editor - das WebUI-Token, per "Anzeigen" lesbar Jetzt gesperrt, bis entsperrt: - Tab "Polymarket Accounts": Gesperrt stehen die Konten gar nicht im visuellen Baum. Das Raster bleibt leer, der Editor mit den Private Keys ebenfalls; nichts ist nur verdeckt. - Master-Key erzeugen, OpenRouter-, Watchdog- und Lizenzschluessel setzen. - Mullvad-Account und Polygon-RPC-URL: mit [SensitiveSetting] markiert und im Editor ausgeblendet. - WebUI-Zugang: Gesperrt steht das Token nicht einmal maskiert im Feld. Speichern ist mitgesperrt, weil es den Feldinhalt als Token schreibt; gesperrt und leer haette es das Token geloescht. - Frei bleiben Lizenzpruefung und Test-Heartbeat, weil sie keinen Schluessel zeigen. Kern im Core (PolyTrader.Core.Security), weil nur dort testbar: - SettingsPasswordStore: PBKDF2-HMAC-SHA256, 600 000 Iterationen, Salt, eigene Datei settings-password.json neben master.key, atomar geschrieben und nur fuer den Besitzer lesbar. Nicht in server_settings.xml, die seit #561 das WebUI mitschreibt. Ein vorhandenes Passwort wird nie ueberschrieben. - SettingsLock: - erstes Festlegen mit Wiederholung und mindestens 10 Zeichen - zwei freie Fehlversuche, danach 5 s bis 5 min Wartezeit, in der auch das richtige Passwort nicht geprueft wird - Sperre nach 5 Minuten ohne geschuetzte Aktion und beim Verlassen der Einstellungen - eine unlesbare Passwortdatei entsperrt nie und gilt auch nicht still als "kein Passwort" - Protokoll ohne eingegebene Werte Oberflaeche: PasswordDialog deklarativ in .axaml, Sperr-Leiste ueber den Tabs. Die PBKDF2-Pruefung dauert etwa eine halbe Sekunde und laeuft nicht im UI-Thread. Grenzen: - Die Sperre schuetzt gegen jemanden an einer entsperrten Desktop-Sitzung. Wer Dateizugriff hat, liest master.key selbst. Deshalb gibt es kein "Passwort vergessen": Wer settings-password.json loeschen kann, braucht keins. - Die Wartezeit nach Fehlversuchen beginnt nach einem Neustart von vorn. - "Datenbankeinstellungen": Eine Ansicht dafuer gibt es im Desktop nicht; die Verbindungszeichenfolge steht nur in appsettings.Local.json. Entsteht eine, gehoert sie hinter dieselbe Sperre. Tests: 20 fuer Passwortdatei und Sperre (Festlegen, Fehlversuche, Wartezeit, Leerlauf, unlesbare Datei, Aendern, Iterationszahl aus der Datei, Dateirechte) und 5 fuer die Markierung der Geheimnisfelder. Die Oberflaeche selbst wird auf der VM abgenommen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
48 lines
1.8 KiB
C#
48 lines
1.8 KiB
C#
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using PolyTrader.Core.Security;
|
|
using PolyTraderSharp.Models;
|
|
using Xunit;
|
|
|
|
namespace PolyTrader.Tests
|
|
{
|
|
/// <summary>
|
|
/// Geheimnisse in den Server-Einstellungen sind entweder ganz ausgeblendet oder als
|
|
/// <see cref="SensitiveSettingAttribute"/> markiert (#567). Der Desktop-Editor zeigt markierte Felder nur
|
|
/// entsperrt; ein neues Geheimnisfeld ohne Markierung stünde dort sonst offen im Fenster.
|
|
/// </summary>
|
|
public class SensitiveSettingsTests
|
|
{
|
|
private static PropertyInfo Prop(string name) => typeof(ServerSettings).GetProperty(name)!;
|
|
|
|
[Theory]
|
|
[InlineData(nameof(ServerSettings.MullvadAccount))]
|
|
[InlineData(nameof(ServerSettings.PolygonRpcUrl))]
|
|
public void Sichtbare_Geheimnisfelder_sind_markiert(string name)
|
|
{
|
|
Assert.NotNull(Prop(name).GetCustomAttribute<SensitiveSettingAttribute>());
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(nameof(ServerSettings.WatchdogToken))]
|
|
[InlineData(nameof(ServerSettings.LicenseKey))]
|
|
public void Token_und_Lizenzschluessel_sind_ganz_ausgeblendet(string name)
|
|
{
|
|
Assert.False(Prop(name).GetCustomAttribute<BrowsableAttribute>()?.Browsable ?? true);
|
|
}
|
|
|
|
[Fact]
|
|
public void Keine_weiteren_Felder_sind_versehentlich_markiert()
|
|
{
|
|
var marked = typeof(ServerSettings).GetProperties()
|
|
.Where(p => p.GetCustomAttribute<SensitiveSettingAttribute>() != null)
|
|
.Select(p => p.Name)
|
|
.OrderBy(n => n)
|
|
.ToArray();
|
|
|
|
Assert.Equal(new[] { nameof(ServerSettings.MullvadAccount), nameof(ServerSettings.PolygonRpcUrl) }, marked);
|
|
}
|
|
}
|
|
}
|