Das Repo enthaelt ab jetzt ausschliesslich plattformneutralen Code. Build gruen (0 Fehler), 476 Tests gruen, Linux-Publish der Avalonia-App verifiziert. Rueckfallebene ist der Tag winforms-final, der jetzt auch auf dem Server liegt. WinForms endgueltig ausgebaut (P11/L5): - PolyTrader.App.csproj aus PolyTraderSharp.sln genommen und geloescht - Ui/, Models/, Licensing/, services/, Properties/, Resources/icons/, Program.cs, favicon.ico entfernt - rund 4.500 LOC generierter Designer-Code - Root-appsettings.json entfernt: exaktes Duplikat der Avalonia-Kopie, die die App tatsaechlich liest. Zwei Dateien mit gleichem Inhalt laufen frueher oder spaeter auseinander. - Kein net10.0-windows mehr im Repo; Build-Warnungen von 23 auf 15 gesunken (die CS0169 aus den Designer-Resten sind weg) PNG-Symbole gerettet statt geloescht: Die Avalonia-App band sie per ..\..\Resources\*.png aus dem Repo-Root ein und haette sie mitverloren. Sie liegen jetzt in src/PolyTrader.App.Avalonia/Assets/, wo sie hingehoeren. Der avares-Pfad "Assets/<datei>.png" bleibt unveraendert - in der gebauten Assembly nachgeprueft. LicenseLabrador abgeloest (D-6): - LicenseLabrador.Client aus lib/nuget entfernt, Quellen-Mapping in NuGet.Config auf Deploymentcenter.* reduziert, lib/nuget/README.md neu - Bewusste Abweichung vom Plan: die Watchdog*-Felder in ServerSettings bleiben. D-1 hat sie auf die Deploymentcenter-API umgewidmet statt sie zu ersetzen; sie werden aktiv benutzt. Die Planzeile stammte aus der Zeit davor. Weitere Altlasten: - agentspace/ (30 Dateien: WinForms-Designer-Patcher, fix_mongo.py nach der MySQL-Migration, tmp/test/scratch-Skripte) entfernt - Ideen-fuer-Mittwoch.txt entfernt - Inhalt ist laengst umgesetzt - tote .gitignore-Regel fuer agentspace/antigravity/ entfernt - lokal entfernt (nicht versioniert): MongoDB/-Exporte, data.db, .bak-Datei Dokumentation nachgezogen: - ANALYSE-Linux-Portierung.md auf Revision 6, P11/L5 als erledigt - LEITFADEN: Abschnitt B vollstaendig aufgeloest, A5 prueft ab jetzt gegen einen Worktree des Tags statt gegen den Arbeitsbaum - UMSETZUNGSPLAN-Watchdog-LicenseLabrador-Integration.md als ABGELOEST gekennzeichnet, D-6 im Deploymentcenter-Plan auf den Iststand gezogen Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
33 KiB
Umsetzungsplan: Watchdog & LicenseLabrador in PolyTrader einbinden
⛔ ABGELÖST — nicht mehr umsetzen
Dieser Plan wird nicht ausgeführt. Watchdog und LicenseLabrador als getrennte Dienste sind durch das Deploymentcenter ersetzt, das Lizenz, Monitoring, Fehler-Reporting und Updates in einem Dienst zusammenfasst.
Maßgeblich ist stattdessen: UMSETZUNGSPLAN-Deploymentcenter-Integration.md
Vollzogen am 22.08.2026 (Schnitt D-6):
LicenseLabrador.Clientist auslib/nugetund aus dem Quellen-Mapping inNuGet.Configentfernt,Licensing/LicenseGate.csundUi/LicenseDialog.cssind mit dem WinForms-Ausbau gelöscht. Im Code findet sich LicenseLabrador nur noch als historische Erwähnung in Kommentaren und Plandokumenten. DieWatchdog*-Felder inServerSettingssind bewusst geblieben: Schnitt D-1 hat sie auf die Deploymentcenter-API umgewidmet statt sie zu ersetzen — sie sind in Gebrauch.Serverseitig noch offen:
watchdog.mhdf.deundlicense.mhdf.deabschalten. Das darf erst geschehen, wenn die Live-Abnahme des Deploymentcenters steht — vorher fehlt die Rückfallebene. Mit dem Abschalten erledigen sich auch die beiden Auflagen aus diesem Plan (Watchdog-Secrets rotieren, UTC/NOW()-Mix).Der Text unten bleibt als Verlaufsdokument stehen. Die Dateiverweise darin zeigen auf inzwischen gelöschte Pfade; ihr Inhalt liegt im Git-Tag
winforms-final.
Stand: 2026-07-28 · Grundlage: Review von
J:\Softwareprojekte\WatchDogundJ:\Softwareprojekte\LicenseLabrador. Reihenfolge war bindend: Teil A (Blocker in den Fremdprojekten) vor Teil B (Einbindung). Wer B ohne A baut, bindet bekannte Lücken fest in den Produktivbetrieb ein.
0. Kurzfassung der Bewertung
LicenseLabrador (Server) ist handwerklich gut: keine Secrets im Repo, Argon2id, CSRF in allen Admin-Formularen, durchgängig Prepared Statements, Ed25519 über den Base64-Payload (umgeht das Kanonikalisierungsproblem elegant), Nonce-Reflexion, Rate-Limit plus Failed-Key-Sperre, Audit-Log. Der Server kann so bleiben. Die Schwächen liegen im C#-Client.
Watchdog ist funktional weit, hat aber Produktiv-Secrets im Git, ein hartkodiertes Admin-Passwort und eine Autorisierung, die jeden Agent-Token wie einen Admin behandelt. Vor der Anbindung von PolyTrader muss dort aufgeräumt werden.
Kritischster Einzelbefund für unseren Betrieb: die Hardware-ID von LicenseLabrador ist auf einem Rechner mit Mullvad-VPN nicht stabil (siehe L-1). Ohne Fix sperrt PolyTrader sich selbst aus.
0a. Nachprüfung vom 29.07.2026
Beide Projekte wurden nach dem ersten Review nachgebessert. Stand jetzt:
LicenseLabrador — integrationsbereit. Die vier Blocker sind behoben (Änderungen liegen noch uncommitted im Arbeitsverzeichnis, sollten also committet werden):
- L-1 erledigt.
HardwareIdnutzt jetztMachineGuid(bzw./etc/machine-id) als Primärquelle,ProcessorCountist raus, der MAC-Fallback filtert virtuelle Adapter (Mullvad, WireGuard, Docker, Hyper-V, WSL) und sortiert deterministisch. Damit ändert ein VPN-Wechsel die Hardware-ID nicht mehr — die Selbst-Aussperrung ist vom Tisch. - L-2 erledigt. Der gecachte Envelope wird gegen die lokale
hardware_idgeprüft; kopiertestate.dat-Dateien werden verworfen. - L-3 erledigt.
signatureTamperedliefert jetztTamperSuspectedstatt still in die Offline-Kulanz zu fallen. - L-5 erledigt. Das HMAC-Secret wird aus der Hardware-ID abgeleitet, und
docs/SECURITY.mdführt es korrekt als Laufzeit-Schutz gegen Property-Patching, nicht als kryptografische Sicherheit. - Das SDK baut fehlerfrei (eine
CS8604-Warnung inLicenseClient.cs:47). - Offen: L-4 (gecachte Endpoints haben weiter Vorrang vor der Konfiguration,
http://wird weiter akzeptiert) sowie L-6 bis L-13. L-4 entschärfen wir auf unserer Seite, indem wir ausschließlichhttps://-Endpoints konfigurieren — da die Antwort signaturgeprüft ist, kann ein untergeschobener Endpoint keine gültige Lizenz erfinden, sondern uns höchstens in die Offline- Kulanz drängen. Kein Blocker mehr, aber Nachtrag wert.
Watchdog — integrationsbereit mit zwei Auflagen. Commits 1e453f8 und 404e184 haben die
Blocker abgearbeitet; nachgeprüft am 29.07.2026:
- Erledigt in
1e453f8: W-2 (Backdoor entfernt, nur nochpassword_verify), W-3 (CSRF inadmin.phpundindex.php), W-4 (alle XSS-Vektoren escaped, inklusivee.message), W-5 (Token-Scoping greift:validateToken()wird benutzt,$requireAdmin()schütztmonitor/edit,monitor/delete,icon/upload), W-6 (Status-API nicht mehr offen), W-13 (Alarm nur noch bei Zustandswechsel, zusätzlich 120 s Dämpfung pro Quelle und Art). - W-14 erledigt in
404e184.Evaluator.phpsetzt einen Monitor nur noch aufup, wennlast_status === 'ok'und die Metriken unauffällig sind. Zustandsmaschine durchgespielt: dauerhaftesstatus=errorbleibt auferror(ein Alarm, danach Ruhe); Rückkehr aufokerzeugt genau eine Entwarnung; Absturz → genau ein Down-Alarm;down+ Rückkehr mitstatus=error→ einhard_error. Die Flapping-Schleife ist weg. - W-22 erledigt.
escapeHtmlliegt jetzt auf Dateiebene (index.php:630) vor allen Verwendungen. Monitore ohne Metriken — unser Fall — brechen im Detail-Fenster nicht mehr ab. - W-23 erledigt.
session_start()steht am Anfang vonapi.php; der Admin-Session-Zweig funktioniert. - W-24 erledigt.
display_errorsund Stack-Traces hängen anWATCHDOG_DEBUG=1; API-Fehler liefern sauberes JSON ohne interne Details. - W-1 teilweise erledigt — Auflage 1. Aus dem getrackten
config/config.phpsind die echten Werte raus (Platzhalter),config.example.phpist ergänzt,config.local.phpwar schon gitignoriert und die Merge-Logik ist intakt. Aber die Secrets stehen weiter in der Git-History:git show 1e453f8:config/config.phpliefert DB-Passwort,shared_keyundsetup_tokenim Klartext. Das lässt sich durch Bearbeiten von HEAD nicht heilen — alle drei müssen rotiert werden (das FTP-Passwort ausdeploy_config.jsongleich mit, es ist dasselbe). - Auflage 2 — W-16 (UTC vs.
NOW()). Unverändert:cli/tick.php:39,44,106,ProxmoxPoller.php:56,117undadmin.php:115,157mischen UTC-Strings aus PHP mit demNOW()der DB-Session. Steht die MySQL-Zeitzone nicht auf UTC, verschieben sich Job-Fälligkeit, Lock-Leases und der Log-Cleanup um den Offset. Entweder in der DB-SessionSET time_zone = '+00:00'setzen oderNOW()durchgängig durch UTC-Parameter ersetzen. Nicht integrationsblockend, aber vor dem Produktivbetrieb zu klären. - Weiterhin offen, ohne Einfluss auf unsere Anbindung: W-11 (
api.php:157gibt im eigenencatchweiter$t->getMessage()heraus — der neue Handler inAutoload.phpmacht es richtig, wird hier aber umgangen), W-15 (intervalSecondsder PS-/Bash-Agents wird weiter ignoriert; unser C#-Client sendetintervalund ist korrekt), W-17 (self_pingist weiter einecho-Stub — es gibt also keinen Alarm, wenn der Watchdog selbst stirbt), W-7 bis W-10, W-12, W-18 bis W-21.
Konsequenz für die Reihenfolge: Schritt 1 und 2 sind erledigt. Teil B kann vollständig beginnen — B.1 bis B.3 (Heartbeat) und B.4/B.5 (Lizenz) sind nicht mehr blockiert. Parallel abzuarbeiten: Secrets rotieren, W-16, W-17.
Teil A — Blocker in den Fremdprojekten
A.1 Watchdog — Sicherheit
| # | Befund | Datei | Maßnahme |
|---|---|---|---|
| W-1 | DB-Passwort, shared_key, setup_token als Defaults im getrackten config/config.php (auch in der Git-History) |
config/config.php:15,19,20 |
Datei nach config.php.example umbenennen (Muster von LicenseLabrador), aus dem Index nehmen, alle drei Secrets rotieren |
| W-2 | Hartkodiertes Admin-Passwort, vor der DB-Prüfung ausgewertet | public/admin.php:31 |
Ersatzlos entfernen; nur noch users-Tabelle + password_verify |
| W-3 | Kein CSRF-Schutz im Admin — Monitor löschen, Proxmox-Target anlegen, Token widerrufen sind cross-site auslösbar | public/admin.php (alle POST-Zweige) |
Csrf-Klasse aus LicenseLabrador übernehmen (1:1 portierbar) |
| W-4 | Stored XSS: Heartbeat-message und Disk-Namen landen ungefiltert per innerHTML im Dashboard |
public/index.php:707,731,744,770 |
textContent statt innerHTML, oder clientseitig escapen |
| W-5 | Jeder gültige Agent-Token autorisiert jeden Endpunkt — auch /api/monitor/delete, /api/monitor/edit, /api/link, /api/icon/upload |
public/api.php:66-77 |
Zwei Berechtigungsstufen trennen: Agent-Token nur für heartbeat/event; verwaltende Routen nur mit Admin-Session oder shared_key. TokenManager::validateToken() (prüft die Source-Bindung) wird derzeit nirgends aufgerufen — einbinden |
| W-6 | /api/status, /api/events, /api/lineage sind bewusst unauthentifiziert und zusätzlich von der Basic-Auth ausgenommen |
public/api.php:60, .htaccess:6 |
Für uns nicht akzeptabel: „polytrader ist seit 14:03 down" ist eine verwertbare Information. Lesezugriff hinter Session oder eigenen Read-Token legen |
| W-7 | Installer-Download läuft vor dem Login-Check; {{TOKEN}}/{{SOURCE}} werden ungefiltert in ein PowerShell-Skript interpoliert, das als SYSTEM ausgeführt wird |
public/admin.php:58-84 |
Hinter $isLoggedIn verschieben, Platzhalter strikt validieren ([A-Za-z0-9_-]), Host nicht aus HTTP_HOST ableiten |
| W-8 | Agent-Token als maschinenweite Umgebungsvariable, Default-URL http:// |
agents/windows/install.ps1.tmpl:104, agents/*/watchdog-agent.*:9,4 |
Token in eine ACL-geschützte Datei unter C:\ProgramData\Watchdog; HTTPS erzwingen |
| W-9 | SVG-Upload in denselben Origin, mkdir(0777) |
public/api.php:284-296, admin.php:240 |
SVG entfernen oder als Content-Disposition: attachment ausliefern; 0755 |
| W-10 | Proxmox: allowSelfSigned = true als Default, cert_fingerprint nie benutzt, Token-Secret im Klartext in der DB |
src/ProxmoxClient.php:18,76, schema:87 |
Fingerprint-Pinning umsetzen (steht so in HANDOFF §8), Secret verschlüsseln |
| W-11 | 500-Antwort gibt $t->getMessage() an den Client (DB-Fehlerdetails) |
public/api.php:141-144 |
Generische Meldung, Details ins event_log |
| W-12 | FTP-Deploy-Passwort im Klartext, identisch mit DB- und Admin-Passwort | scripts/deploy_config.json |
Eigenes Passwort, rotieren |
A.2 Watchdog — funktionale Fehler
| # | Befund | Wirkung |
|---|---|---|
| W-13 | api.php:120 alarmiert bei jedem Heartbeat mit status=error/warning sofort |
Ein dauerhaft fehlerhafter Dienst im 30-s-Takt erzeugt 120 Telegram-Nachrichten pro Stunde. Widerspricht §6 des Umsetzungsplans („nur bei Zustandswechsel") |
| W-14 | upsertHeartbeat erzwingt state = IF(state="down","up",…), der Evaluator setzt bei frischem Heartbeat zusätzlich error → up („recovered") |
Ein Dienst, der dauerhaft status=error meldet, pendelt im Minutentakt zwischen „recovered" und „hard_error" — mit Alarm bei jedem Wechsel |
| W-15 | Agents senden intervalSeconds, die API liest nur interval/expected_interval_sec |
Der gemeldete Takt wird still verworfen, es bleibt bei 60 s. Der C#-Client sendet interval und ist zufällig korrekt |
| W-16 | PHP schreibt UTC-Strings, MySQL vergleicht mit NOW() (Session-Zeitzone) |
Job-Fälligkeit, Lock-Leases und Log-Cleanup verschieben sich um den UTC-Offset, sofern die DB nicht auf UTC steht. Betrifft cli/tick.php:39,44,106 und admin.php:108 |
| W-17 | self_ping ist ein echo-Stub, proxmox_smart hat keinen Handler, first_contact_deadline_utc / expect_running / ack_until_utc werden nie ausgewertet |
Der „Wer wacht über den Wächter"-Mechanismus fehlt komplett; der Nie-Start-Alarm ebenfalls. Beides sind Abnahmekriterien aus dem Plan |
| W-18 | warning_digest schickt HTML durch notifyImmediate, das per htmlspecialchars escaped |
Im Telegram erscheinen <b>-Tags als Text |
| W-19 | Nur der Heartbeat-Zweig liegt im try/catch, danach läuft der Code ohne Fehlerbehandlung weiter |
Uneinheitliche Fehlerbilder |
| W-20 | Kein Rate-Limit auf /api/heartbeat (Plan §14 fordert es) |
Ein durchdrehender Client flutet die DB |
| W-21 | Keine Tests im gesamten Projekt (Plan §13 fordert Unit-Tests der Zustandsmaschine) | W-13/W-14 wären mit einem Test aufgefallen |
Mindestumfang vor der Einbindung: W-1, W-2, W-5, W-13, W-14, W-16. Ohne W-13/W-14 ist der Watchdog für einen produktiven Trading-Bot unbrauchbar, weil jeder echte Fehler in einer Nachrichtenflut untergeht.
A.3 LicenseLabrador — Client
| # | Befund | Datei | Maßnahme |
|---|---|---|---|
| L-1 | Hardware-ID ist instabil. Die „erste" aktive Netzwerkkarte wird per FirstOrDefault ohne stabile Sortierung gewählt, dazu ProcessorCount |
HardwareId.cs:47, :40 |
Blocker für uns. Mullvad, Hyper-V und Docker legen virtuelle Adapter an und wieder ab — jede Änderung erzeugt eine neue HWID, damit eine neue Aktivierung, damit max_activations (Default 2) erschöpft: PolyTrader sperrt sich selbst aus. Fix: nur MachineGuid als Primärquelle; MAC nur als Fallback, wenn die Registry nichts liefert, und dann sortiert und auf physische Adapter gefiltert; ProcessorCount entfernen |
| L-2 | Offline-Cache ist maschinen-portabel: geprüft wird nur payload.license_key == licenseKey, nicht hardware_id und nicht product |
LicenseClient.cs:195 |
Wer state.dat von einer aktivierten Maschine kopiert und das Netz blockiert, läuft bis zu 168 h auf beliebig vielen Rechnern. Der wirksamste Angriff auf das gesamte System — und mit zwei Vergleichen behoben |
| L-3 | Erkannte Signaturmanipulation (signatureTampered) wird verworfen und fällt still in die Offline-Kulanz |
LicenseClient.cs:132-135 |
LicenseState.TamperSuspected zurückgeben und protokollieren |
| L-4 | Kein TLS-Zwang; Endpoints aus dem Cache haben Vorrang vor der Konfiguration | LicenseClient.cs:276-292, :342 |
http://-Endpoints ablehnen; konfigurierte Endpoints zuerst versuchen, gecachte nur als Ergänzung |
| L-5 | HMAC-„Härtung" mit Klartext-Konstante im Assembly, geprüft mit demselben Prozess-Secret | LicenseClient.cs:23,172 |
Schützt gegen nichts, was ein Angreifer nicht in derselben Sitzung mitpatcht. Als Obfuskation behalten, aber in docs/SECURITY.md nicht mehr als Schutzmaßnahme führen |
| L-6 | Offline-.lic ohne Hardware-Bindung bzw. ohne Key gilt für jede Maschine bzw. jeden Key |
LicenseClient.cs:232,242, offline_license.php:62 |
Im Admin-Dialog hardware_id verpflichtend machen (Ausnahme nur mit expliziter Warnung); ein geleaktes Notfall-File ist sonst ein Universal-Crack bis valid_until |
| L-7 | Session-Cookie ohne secure-Flag |
server/src/Auth.php:11 |
'secure' => true + HTTPS erzwingen |
| L-8 | key_id wird signiert und übertragen, vom Client aber ignoriert |
Signature.cs |
Ohne Auswertung ist keine Schlüsselrotation möglich, ohne alle Clients gleichzeitig zu tauschen. Für uns momentan verschmerzbar, aber notieren |
| L-9 | LicenseClient erzeugt einen HttpClient pro Instanz und ist nicht IDisposable |
LicenseClient.cs:28 |
Socket-Leak bei wiederholter Instanziierung; HttpClient injizierbar machen |
| L-10 | app_version hartkodiert "1.0.0" |
LicenseClient.cs:325 |
Aus LicenseConfig befüllen — der Server pflegt die Spalte bereits |
| L-11 | strtotime() liefert bei ungültigem Datum false → valid_until: false im JSON → Deserialisierung wirft → stilles „keine Lizenz" |
offline_license.php:55 |
Datum validieren |
| L-12 | Nur 5 Unit-Tests; EvaluateOfflineFallback, Statusabbildung und Grace-Ablauf sind ungetestet |
ClientTests.cs |
Tests für L-1/L-2 mitliefern |
| L-13 | create_admin.php: Minimum 6 Zeichen, Passwort wird im Klartext angezeigt |
server/tools/create_admin.php |
Minimum 12, Eingabe verdecken |
Mindestumfang vor der Einbindung: L-1, L-2, L-3, L-4.
Teil B — Einbindung in PolyTrader
B.0 Leitentscheidungen
- Eigene Client-Implementierung für den Watchdog. Der mitgelieferte
WatchdogTestClientdeaktiviert die Zertifikatsprüfung vollständig (WatchdogClient.cs:42). Wir schreiben einen schlankenWatchdogHeartbeatServiceinPolyTrader.Core— der HTTP-Vertrag ist trivial (ein POST mit JSON und einem Header) und wir behalten die Kontrolle über TLS, Timeouts und Fehlerverhalten. - LicenseLabrador.Client als Projektreferenz, nicht als kopierter Code — dann fließen Fixes aus Teil A.3 automatisch ein. Voraussetzung: L-1 bis L-4 sind dort behoben.
- Kein
Environment.Exitbei ungültiger Lizenz.docs/INTEGRATION.mdschlägt das vor; für einen Trading-Bot mit offenen Positionen ist ein harter Abbruch gefährlich. Stattdessen: Trading-Module gar nicht erst starten (Startfall) bzw. geordnet stoppen (Laufzeitfall). - Secrets über
SecretProtection. Watchdog-Token und Lizenzschlüssel werden mit dem bestehenden AES-GCM-Master-Key (POLYTRADER_MASTER_KEY) verschlüsselt abgelegt — dieselbe Mechanik wie für Wallet-Keys. - Heartbeat-Metriken bleiben schlank. Version, aktive Module, Uptime. Keine Kontostände, Positionen, Wallet-Adressen oder Trader-Namen — der Watchdog-Server liegt beim Hoster und seine Status-API war bis W-6 öffentlich lesbar.
B.1 Slice W-A — Heartbeat-Grundgerüst ✅ ERLEDIGT (29.07.2026)
Umgesetzt in WatchdogHeartbeatService.cs mit 21 Tests in WatchdogHeartbeatTests.cs. Abweichungen und Ergänzungen gegenüber der Planung unten:
- TLS-Zwang implementiert:
TryBuildEndpointakzeptiert nurhttps://;http://ausschließlich für Loopback-Adressen (lokales Testen). Damit kann der Agent-Token nicht versehentlich im Klartext übertragen werden — der Grund, warum der mitgelieferte Testclient nicht verwendet wird. - Intervall wird nach unten begrenzt (10 s), damit ein Fehlkonfigurierter Client den Watchdog nicht flutet (W-20 ist serverseitig weiter offen).
- Log-Entprellung: nur der erste Fehlversuch und die Erholung werden protokolliert, nicht jeder Tick. Sonst hätte ein längerer Watchdog-Ausfall das Terminal geflutet.
- Settings-Quelle injizierbar (
Func<ServerSettings>), damit der Dienst ohne Datei testbar ist. - Token darf verschlüsselt sein:
SecretProtection.Unprotectreicht Klartext unverändert durch und entschlüsseltenc:v1:…, wenn ein Master-Key gesetzt ist. Kein neuer Fehlerpfad, wenn kein Master-Key existiert. - UI — vollständig über das Settings-Fenster bedienbar: Enabled, URL, Source, Instance und
Intervall erscheinen automatisch im bestehenden
PropertyGrid(Kategorie „Watchdog Monitoring"). Der Agent-Token ist bewusst[Browsable(false)]und wird über den Designer-Button „Watchdog-Token setzen …" mit maskierter Eingabe gepflegt — kein Secret offen im Fenster und kein Handanlegen an derserver_settings.xml. Bei gesetztem Master-Key wird er überSecretProtection.Protectverschlüsselt abgelegt, sonst mit deutlichem Hinweis im Klartext. Die schreibgeschützte AnzeigeWatchdogTokenStatuszeigt „nicht gesetzt" / „gesetzt (verschlüsselt)" / „gesetzt (Klartext)". Zweiter Button: „Test-Heartbeat senden" — nutzt die gespeicherten Werte, damit ein Erfolg keine Konfiguration bestätigt, die nicht auf der Platte liegt. - Änderungen greifen ohne Neustart.
ExecuteAsyncwertet die Konfiguration in jedem Durchlauf neu aus (1-s-Poll, Sendezeitpunkt über eine Fälligkeit, der Takt bleibt also exakt) statt einenPeriodicTimermit fest verdrahtetem Intervall zu benutzen. Ein-/Ausschalten, Takt-, URL- und Token-Änderungen wirken sofort nach dem Speichern; Zustandswechsel werden je Ursache genau einmal geloggt. Zwei Tests fixieren das Live-Ein- und -Ausschalten.
Ursprüngliche Planung:
Neu: src/PolyTrader.Core/Services/WatchdogHeartbeatService.cs (BackgroundService)
ExecuteAsync:PeriodicTimerim konfigurierten Takt (Default 60 s). Jeder Sendeversuch intry/catch— ein nicht erreichbarer Watchdog darf PolyTrader niemals beeinträchtigen.- Payload:
source,instance,type: "heartbeat",status,message,interval(nichtintervalSeconds, siehe W-15),group,metadata. - Header
X-Watchdog-Keymit dem entschlüsselten Agent-Token. HttpClientausSocketsHttpHandlerwie die übrigen Clients inProgram.cs, mit Standard-Zertifikatsprüfung und 10 s Timeout.- Registrierung in
Program.csnebenMarketSyncService:services.AddHostedService<WatchdogHeartbeatService>();
Konfiguration in ServerSettings (src/PolyTrader.Core/Models/ServerSettings.cs), analog zu
den Threema-/Mullvad-Feldern:
WatchdogEnabled bool = false
WatchdogUrl string = "" // https:// erzwungen
WatchdogSource string = "polytrader"
WatchdogInstance string = "prod-1"
WatchdogIntervalSeconds int = 60
WatchdogToken string = "" // via SecretProtection verschlüsselt
Bedienung über die bestehende SettingsView (Designer-fähig, partial + .Designer.cs — siehe
UI-Regel), mit einem „Test-Heartbeat senden"-Button.
Abnahme: Monitor erscheint im Watchdog-Dashboard, last_seen_utc läuft mit. PolyTrader startet
und arbeitet unverändert, wenn WatchdogUrl falsch oder der Server aus ist.
B.2 Slice W-B — Status aus dem Log ableiten ✅ ERLEDIGT (31.07.2026)
Umgesetzt im WatchdogHeartbeatService. Abweichungen gegenüber der Planung:
- Nur
Erroreskaliert, keine Warnungs-Eskalation: App-Warnungen (VPN-Reconnect, „kein Master-Key" …) sind zu häufig, um daraus einen Monitor-Alarm zu rechtfertigen.Warning/Infolassen den Status aufok. Das vermeidet auch eine Rückkopplung, wenn der Dienst selbst warnt. - Entprellung über ein Nachwirkfenster (
ErrorStickyWindow = 5 min): Nach einemErrormeldet der Heartbeat so langestatus=error, bis 5 Minuten ohne neuen Fehler vergangen sind — dann wiederok. Kein Flackern error→ok→error, Erholung wird genau einmal gemeldet. (Der 30-Minuten- Notbehelf aus der Planung entfällt, weil W-13 im Watchdog behoben ist und ohnehin nur bei Zustandswechsel alarmiert wird.) Sanitize()entfernt Wallet-Adressen/Keys (0x…-Hex) und verschlüsselte Secrets (enc:v1:…) und kürzt auf 200 Zeichen, bevor eine Fehlermeldung an den (extern gehosteten) Watchdog geht. Beträge werden bewusst nicht gestrippt (würde die Meldung entwerten; der Kanal ist nur für den Betreiber).- Angedockt über
TerminalLogger.OnLogMessage(synchron), Abmeldung inDispose.
Ursprüngliche Planung: Der Heartbeat soll status=error melden, wenn PolyTrader in Not ist.
TerminalLogger.OnLogMessageabonnieren, Fehler der letztennIntervalle zählen.- Ableitung:
Errorim Fenster →status=errormit der letzten Fehlermeldung alsmessage;Warning→status=warning; sonstok. - Entprellen ist Pflicht, solange W-13 im Watchdog nicht behoben ist: sonst löst jeder
Heartbeat eine eigene Telegram-Nachricht aus. Bis dahin
status=errorfrühestens alle 30 Minuten erneut senden. - Meldungstexte kürzen (≤ 200 Zeichen) und keine Wallet-Adressen, Keys oder Beträge durchreichen. Ein Filter über die bekannten Secret-Präfixe gehört dazu.
B.3 Slice W-C — Lifecycle-Events ✅ ERLEDIGT (31.07.2026)
Umgesetzt im WatchdogHeartbeatService (SendEventAsync → POST /api/event):
- Start:
kind: "started"mit App-Version, genau einmal, sobald der Dienst aktiv wird (in der Sendeschleife, nachAppHost.Start()), vor dem ersten Heartbeat. - Geplantes Beenden:
StopAsyncsendetkind: "stopped_graceful"(best effort, 3-s-Timeout, blockiert das Herunterfahren nie). Läuft über den bestehenden PfadAppHost.StopAsync()nachApplication.Run+ den 10-Sekunden-Bestätigungsdialog. - Damit unterscheidet der Watchdog „sauber beendet" (Monitor →
stopped, kein Alarm) von „abgestürzt" (Staleness → genau ein Down-Alarm) — der eigentliche Mehrwert. - Tests decken „started"→„stopped_graceful" über den Dienst-Lebenszyklus sowie den
/api/event-Vertrag ab.
Abnahme (manuell, live): PolyTrader über die Menüleiste beenden → Monitor geht auf stopped,
kein Alarm. PolyTrader per Task-Manager abschießen → nach Interval × Faktor + Grace genau
ein Down-Alarm.
Stand 31.07.2026: Watchdog-Seite (B.1/B.2/B.3) und Lizenz-Seite (B.4, B.5 vereinfacht)
umgesetzt; 438 Tests grün, --smoke-ui ok. Offen nur noch die live-Abnahme (echter Server)
und die Fremdprojekt-Auflagen (Watchdog-Secrets rotieren, UTC/NOW(), self_ping-Stub).
B.4 Slice L-A — Lizenzprüfung beim Start ✅ ERLEDIGT (31.07.2026)
Umgesetzt in Licensing/LicenseGate.cs, Startdialog in
Ui/LicenseDialog.cs (+.Designer.cs), Verdrahtung in
Program.cs, Schlüssel-/Statusfeld in
ServerSettings.cs. Abweichungen und
Ergänzungen gegenüber der ursprünglichen Planung unten:
- Ort:
LicenseGateliegt im App-Projekt (PolyTraderSharp.Licensing), nicht in Core. Grund:Program.Main(App) ist der einzige Aufrufer, und so bleibt die (cross-repo) SDK-Referenz aufPolyTrader.Appbeschränkt statt in Core alle Module zu belasten. - SDK-Einbindung:
LicenseLabrador.Clientals Cross-Repo-Projektreferenz vonPolyTrader.Appauf..\..\LicenseLabrador\...(Fixes am SDK fließen ohne Kopie ein). Baut sauber, kein BouncyCastle-Konflikt mit Nethereum. - Slug
pt(nichtpolytrader), Endpointhttps://license.mhdf.de, eingebetteter Public-Key. End-to-End verifiziert (ohne Aktivierung zu verbrauchen):validatemit bewusst ungültigem Schlüssel liefert signiertesnot_found/„Invalid license key" (⇒ Slugptbestätigt, Produkt existiert), Nonce wird reflektiert, und die Server-Signatur verifiziert gegen den eingebetteten Public-Key (openssl Ed25519 „Verified Successfully"). Der Client wird echte Antworten also akzeptieren. - Startablauf: Master-Key wird jetzt vor dem Host-Build geladen (
ConfigureSecretProtection(bootLog)), dann der Gate. BeiIsUsable == false→ modaler Dialog (Schlüssel eingeben, Hardware-ID anzeigen + kopieren, „eingeschränkt starten"); bleibt es ungültig, wird die Modulliste leer gebaut → nur die Core-Shell (Terminal/Settings) startet. KeinEnvironment.Exit.TamperSuspectedwird als nicht nutzbar behandelt und im Dialog benannt. - Konfiguration wie beim Watchdog-Token:
ServerSettings.LicenseKey([Browsable(false)], bei Master-Key verschlüsselt) + Statusanzeige. Der Schlüssel9KHEZ-…ist bereits inserver_settings.xmlhinterlegt → beim ersten echten Start validiert der Gate automatisch und aktiviert auf dieser Maschine (kein Dialog nötig, falls gültig). - Ein Dialog für beide Wege (31.07.2026): Der Settings-Toolstrip-Button „Lizenz prüfen /
setzen …" öffnet denselben
LicenseDialogwie der Start, nur im Verwalten-Modus (startupContext:false, Button „Schließen" statt „eingeschränkt starten"). Damit validiert man auch aus den laufenden Einstellungen gegen den Server (Hardware-ID sichtbar/kopierbar); der frühere einfache Text-Prompt entfällt. Ein gültig validierter Schlüssel wird verschlüsselt gespeichert und gilt ab dem nächsten Start. - Verifikation: Solution baut fehlerfrei, 430 Tests grün,
--smoke-uikonstruiert zusätzlichlicense.dialog. Eigene Unit-Tests für den Gate wurden bewusst nicht angelegt: er ist dünner Leim über dem SDK (das seine eigenen Krypto-Tests mitbringt), und ein Test bräuchte eine Referenz vom Testprojekt auf die WinExe. Stattdessen deckt der Smoke-Test Konstruktion/Compile ab. - Offen (bewusst): L-4 bleibt serverseitige Hygiene — sicherstellen, dass die
endpoints- Einstellung im Lizenz-Admin https oder leer ist (gecachte Endpoints hätten sonst im SDK Vorrang). Und die eigentliche erfolgreiche Aktivierung mit dem echten Schlüssel passiert erst beim realen Start aus PolyTrader (mit dessen Hardware-ID) — von hier aus bewusst nicht getestet, um keinen Aktivierungs-Slot auf der falschen Maschine zu verbrennen.
Ursprüngliche Planung:
Neu: src/PolyTrader.Core/Security/LicenseGate.cs
- Wird in
Program.MainnachConfigureSecretProtectionund vorAppHost.Start()aufgerufen — der Lizenzschlüssel liegt verschlüsselt und braucht den Master-Key. LicenseConfig:ProductSlug = "polytrader", eingebetteterPublicKeyBase64, Endpoints ausschließlichhttps://.- Ergebnis:
IsUsable == true→ normaler Start. BeiValidOfflinezusätzlich eine sichtbare Restlaufzeit im Launcher.IsUsable == false→ Lizenzdialog (Schlüssel eingeben, Hardware-ID anzeigen und kopierbar machen), Trading-Module werden nicht registriert. Der Rest der Shell darf starten, damit Terminal und Settings erreichbar bleiben.
TamperSuspected(nach L-3) wird deutlich angezeigt und wie „nicht nutzbar" behandelt.
Wichtig: Der Schlüssel gehört in die verschlüsselte Ablage, nicht in appsettings.json.
B.5 Slice L-B — Revalidierung im Betrieb VERWORFEN (Entscheidung 31.07.2026)
Es gibt bewusst KEINE Laufzeit-Revalidierung. Richards Vorgabe: im laufenden Betrieb darf
lizenzseitig gar nichts passieren — die Software darf niemals mitten in einer Sitzung aufhören
zu arbeiten (offene Positionen, laufende API-Aufrufe). Die einzige Durchsetzung ist der
Startup-Gate (B.4): beim nächsten Start einer ungültigen/widerrufenen/kopierten Lizenz startet
die Software nicht mehr voll, sondern zeigt die Aufforderung, eine (neue) gültige Lizenz anzugeben.
Kein BackgroundService, kein 12-h-Timer, kein Netzwerkaufruf während der Sitzung.
Warum das reicht (Bedrohungsmodell = Schutz vor einfachem Kopieren auf ein fremdes System): Die Software läuft überwiegend auf eigenen Systemen. Geschützt werden soll nur gegen „jemand kopiert die Software auf ein eigenes System und macht dort weiter". Das leistet der Startup-Gate durch drei Mechanismen des SDK, alle beim Start:
- Hardware-gebundene Aktivierung — ein fremdes System hat eine andere
MachineGuid⇒ andere Hardware-ID ⇒ braucht einen eigenen Aktivierungs-Slot. max_activations— sind alle Slots belegt, liefert der Serveractivation_limit⇒ die Kopie startet nicht (nur die Aufforderung erscheint).- Hardware-gebundener Offline-Cache (L-2-Fix) — ein mitkopierter
state.datgehört zu einer fremden Hardware-ID und wird offline verworfen ⇒ die Kopie kann sich auch ohne Netz nicht „durchmogeln".
Zwei Stellschrauben im Lizenz-Admin (Produkt pt):
max_activationsgroßzügig auf die Zahl der eigenen Maschinen + Reserve setzen (ein Windows-Neuinstall erzeugt eine neueMachineGuid= neuer Slot; alte Aktivierungen im Admin freigeben). Sonst sperrt man sich bei einem Reinstall selbst aus.default_cache_ttl_hours(Offline-Kulanz, Default 168 h/7 Tage) = wie lange eine eigene, bereits aktivierte Maschine ohne Serverkontakt neu starten darf. Für rein eigene Systeme darf das gern länger sein. Die Kopie profitiert davon nicht (Punkt 3).
Verhalten bei fehlender/ungültiger Lizenz beim Start (aktuell umgesetzt): modaler Dialog (Schlüssel eingeben / Hardware-ID kopieren / „eingeschränkt starten"). Ohne gültige Lizenz startet nur die Core-Shell (Terminal + Einstellungen), keine Module — die Software „arbeitet" also nicht, bleibt aber bedienbar genug, um eine Lizenz einzutragen. Wer stattdessen ein hartes Beenden statt des eingeschränkten Modus will, ist eine Ein-Zeilen-Änderung im Dialog (offen).
B.6 Slice L-C — Predictalytics
Beide Dienste sind mandantenfähig ausgelegt: ein zweites Produkt predictalytics im
LicenseLabrador-Admin, ein zweiter Monitor im Watchdog. Der Heartbeat-Service ist bewusst
projektunabhängig geschrieben und lässt sich unverändert übernehmen. Fällt an, sobald
Predictalytics produktiv geht.
Reihenfolge und Aufwand
| Schritt | Inhalt | Abhängigkeit |
|---|---|---|
| 1 | Watchdog A.1 (W-1, W-2, W-5) + A.2 (W-13, W-14, W-16) | — |
| 2 | LicenseLabrador A.3 (L-1 bis L-4) + Tests | — |
| 3 | B.1 Heartbeat-Grundgerüst + Settings-UI | 1 |
| 4 | B.2 Status aus dem Log | 3 |
| 5 | B.3 Lifecycle-Events | 3 |
| 6 | B.4 Lizenzprüfung beim Start | 2 |
| 7 | B.5 Revalidierung | 6 |
| 8 | Restliche Befunde aus A.1/A.2/A.3 | — |
| 9 | B.6 Predictalytics | 3, 6 |
Schritte 1 und 2 sind unabhängig voneinander und können parallel laufen.
Offene Entscheidungen für Richard
- Wo läuft der Watchdog? Aktuell ein Shared-Webserver bei derselben Hosting-Firma. Für den
Zweck („Außensicht auf einen Server-Crash") reicht das, solange es nicht dieselbe Maschine ist.
Der
self_pingan healthchecks.io (W-17) fehlt aber noch komplett — bis dahin gibt es keinen Alarm, wenn der Watchdog selbst stirbt. - Lizenzierung für uns selbst oder für Kunden? Wenn PolyTrader vorerst nur auf unseren eigenen Maschinen läuft, ist die Offline-Kulanz von 168 h großzügig und L-2 entsprechend entspannter. Sobald Dritte die Software bekommen, sind L-1, L-2 und L-6 harte Blocker.
max_activationssollte für unsere eigene Lizenz großzügig gesetzt werden (5+), solange L-1 nicht behoben ist — sonst reicht ein VPN-Wechsel für eine Aussperrung.