fix(app): przegląd — crash transferu, utwardzenie aktualizacji/known-hosts i poprawki bugów#182
Conversation
…hosts i poprawki bugów High - FileTransferPanel: rekurencyjny transfer (upload/download/rozmiar) pomija dowiązania (symlink/junction) i ma limit głębokości — koniec z pętlą → StackOverflow (twardy crash, którego nie łapie DispatcherUnhandledException). Nowy klucz i18n S.sftp.toodeep (EN+PL). Medium - UpdateService: adres pobrania aktualizacji musi być https + host GitHuba (IsTrustedDownloadUrl); przy niepodpisanym buildzie to jedyna twarda kontrola pochodzenia pobieranego exe. - KnownHosts / FtpsCertPinning: Load(dir, out storeUnreadable) — uszkodzony magazyn ⇒ fail-closed (host/cert traktowany jak ZMIANA klucza, ostrzeżenie) zamiast cichego „nowy host?". - FileTransferPanel: drugi transfer nie niszczy trwającego i nie jest porzucany (guard _busy). - XtermControl: nieudany init WebView2 zeruje _ready — ponowna próba re-inicjalizuje zamiast wisieć. - FileTransferPanel: SafeCombine na nazwach ze zdalnego serwera złapane w handlerach (status „niebezpieczna nazwa" zamiast nieobsłużonego wyjątku / cichego no-op w Release). - RestClient: ConfigureAwait(false) na wszystkich awaitach (dekodowanie dużych odpowiedzi poza UI). - RestConsole: kolory JSON z palety (per-motyw) — czytelne w jasnym motywie. Low - RestClient: dekodowanie ciała wg charset z Content-Type (fallback UTF-8). - AtomicFile: Flush(true) przed atomowym rename (trwałość zapisu). - RdpUtils.SplitHostPort: obsługa IPv6 w nawiasach ([::1]:3389). - PasswordGen: gwarancja po ≥1 znaku z każdej wybranej klasy (Fisher-Yates). - RestStore: self-heal z .bak (wzorem EnvironmentStore) — brak utraty kolekcji przy uszkodzeniu. - ReachabilityService: token anuluje ConnectAsync (bez porzuconego, nieobserwowanego zadania). - RestConsole/ServerEditWindow: font Mono zamiast Consolas; IsDefault na „Zapisz". Świadomie odłożone (wyższe ryzyko bez lokalnego builda / decyzje projektowe): reuse HttpClient, RestScript poza wątkiem UI, mikro-wyciek TOCTOU przy zamknięciu w trakcie łączenia, strip nagłówków przy cross-origin redirect, AutomationProperties dla rysowanych zakładek/drzewa, odświeżanie napisów w otwartych oknach przy zmianie języka, sub-12px czcionki i promienie poza skalą Metrics. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KXXgwUeSkZsXYVKRzyMvV9
There was a problem hiding this comment.
AI Code Review by LlamaPReview
🎯 TL;DR & Recommendation
Recommendation: Request Changes
Fixes a critical crash in file transfer due to symlink loops and hardens security for auto-update and known_hosts, but introduces a race condition in WebView2 terminal initialization that can cause infinite hangs on reconnect.
📄 Documentation Diagram
This diagram documents the refactored file transfer flow with depth limit and reparse point protection.
sequenceDiagram
participant User as User
participant FTP as FileTransferPanel
participant LocalFS as Local File System
participant RemoteFS as Remote File System
User->>FTP: Click upload/download
FTP->>FTP: Check _busy guard
alt Already busy
FTP-->>User: Ignore (return false)
else Not busy
FTP->>FTP: Compute total size (with depth limit and reparse point skip)
activate FTP
FTP->>LocalFS: Read directory / file
LocalFS-->>FTP: List entries
FTP->>FTP: Check depth <= MaxTreeDepth && IsReparsePoint?
Note over FTP: Skip reparse points, limit recursion depth
FTP->>RemoteFS: Transfer file / directory
RemoteFS-->>FTP: Progress
deactivate FTP
FTP-->>User: Transfer complete or error
end
🌟 Strengths
- Solid fix for the
StackOverflowExceptioncrash by adding depth limits and ignoring reparse points. - Security hardening:
IsTrustedDownloadUrlfor auto-update and fail-closed behavior for corrupted known_hosts/certificates.
⚡ Key Risks & Improvements (P1)
- src/RdpManager/XtermControl.cs: On failed WebView2 initialization, resetting
_readywithout completing the oldTaskCompletionSourcecreates a race condition where a concurrent reconnect can await an orphaned TCS forever, reintroducing the hang bug under timing-dependent circumstances.
📈 Risk Diagram
This diagram illustrates the race condition risk in WebView2 terminal initialization on reconnect.
sequenceDiagram
participant User as User
participant Xterm as XtermControl
participant WebView as WebView2
participant TCS as TaskCompletionSource
User->>Xterm: Click Connect (first attempt)
activate Xterm
Xterm->>TCS: Create new TCS, assign to _ready
Xterm->>Xterm: await WaitLoadedAsync()
Note over Xterm: UI thread yields here
User->>Xterm: Click Reconnect (second attempt, while first still waiting)
Xterm->>TCS: Read _ready (non-null from first call)
Xterm->>TCS: await _ready.Task (hangs on first TCS)
Note over Xterm: First call fails, catches exception<br/>Sets _ready = null, but does NOT complete old TCS
Note over TCS: Orphaned TCS never completes<br/>R2(P1): Second call waits indefinitely
Xterm-->>User: Hang (infinite wait)
deactivate Xterm
💡 Have feedback? We'd love to hear it in our GitHub Discussions.
✨ This review was generated by LlamaPReview Advanced, which is free for all open-source projects. Learn more.
| catch | ||
| { | ||
| // Inicjalizacja padła (np. brak runtime WebView2 / zablokowany folder danych). Wyzeruj _ready, | ||
| // żeby ponowna próba (przycisk „Połącz ponownie") re-inicjalizowała, zamiast czekać w | ||
| // nieskończoność na TaskCompletionSource, który nigdy się nie ukończy. | ||
| _ready = null; | ||
| throw; | ||
| } |
There was a problem hiding this comment.
P1 | Confidence: High
The comment explains the intent: reset _ready so that a retry (e.g., user clicks “Reconnect”) can re‑initialize instead of hanging on a TaskCompletionSource that will never complete.
However, setting _ready = null without completing the old TaskCompletionSource creates a race condition. Because InitAsync is an async method, a second concurrent call can read _ready as non‑null after the first call has already created a new TCS but before the first call’s exception handler sets it to null. That second caller will await _ready.Task on the orphaned TCS which never completes, causing the “Reconnect” button to hang indefinitely – exactly the symptom the fix intended to eliminate.
In a single‑threaded UI the race window is narrow but real: any await in the happy path (e.g. WaitLoadedAsync, CoreWebView2Environment.CreateAsync) yields the UI thread, so a click on “Reconnect” can arrive while the first initiation is still in flight. The orphaned TCS is never signalled, so the second caller waits forever.
Impact: A rapid double‑click on “Reconnect” after a failed WebView2 initialization will freeze the terminal panel (infinite wait) instead of allowing another retry. This degrades the user experience and reintroduces the original hang bug under timing‑dependent circumstances.
Code Suggestion:
catch (Exception ex)
{
// Complete the old TCS so that any concurrent caller awaiting it gets the exception
// (don't leave it orphaned) before resetting for a clean retry.
_ready?.TrySetException(ex);
_ready = null;
throw;
}Evidence: path:src/RdpManager/XtermControl.cs, method:InitAsync
Cel
Poprawki z przeglądu aplikacji (bugi + utwardzenie bezpieczeństwa + drobny UI). Każde znalezisko było wcześniej zweryfikowane w kodzie.
🔴 High
StackOverflowException, którego nie łapieDispatcherUnhandledException(twardy crash, urwany transfer). Teraz: pomijamy lokalne reparse-pointy + limit głębokości (także zdalnie). Nowy klucz i18nS.sftp.toodeep(EN+PL).🟠 Medium
https+ host GitHuba (IsTrustedDownloadUrl). Przy niepodpisanym buildzie werdyktCurrentUnsignedjest „akceptowalny", więc to jedyna twarda kontrola pochodzenia pobieranego.exe.Load(dir, out storeUnreadable): uszkodzony magazyn ⇒ fail-closed (host/cert traktowany jak zmiana klucza, z ostrzeżeniem) zamiast cichego „nowy host?"._busy)._ready, więc „Połącz ponownie" re-inicjalizuje zamiast wisieć „Connecting…" w nieskończoność.SafeCombinełapany w handlerach (status „niebezpieczna nazwa" zamiast nieobsłużonego wyjątku / cichego no-op w Release).ConfigureAwait(false)na wszystkich awaitach (dekodowanie dużych odpowiedzi nie blokuje wątku UI).🟡 Low
charset(fallback UTF-8).Flush(true)przed atomowym rename (trwałość).[::1]:3389)..bak(wzoremEnvironmentStore).ConnectAsync(bez porzuconego zadania).MonozamiastConsolas(RestConsole);IsDefaultna „Zapisz" (edytor serwera).Świadomie odłożone (osobno)
Wyższe ryzyko bez lokalnego builda lub decyzje projektowe: reuse
HttpClient,RestScriptpoza wątkiem UI, mikro-wyciek TOCTOU przy zamknięciu w trakcie łączenia, strip nagłówków przy cross-origin redirect,AutomationPropertiesdla rysowanych zakładek/drzewa (a11y), odświeżanie napisów w otwartych oknach przy zmianie języka, sub-12px czcionki i promienie poza skaląMetrics.Uwaga do testów
Sygnatury publiczne (
UploadTree/DownloadTree/SafeCombine/SplitHostPort/PasswordGen) zachowane — istniejące testy powinny się kompilować. Nowe przypadki (IPv6, pokrycie klas hasła, fail-closed) warto dodać osobno.🤖 Generated with Claude Code
https://claude.ai/code/session_01KXXgwUeSkZsXYVKRzyMvV9
Generated by Claude Code