Skip to content

fix(app): przegląd — crash transferu, utwardzenie aktualizacji/known-hosts i poprawki bugów#182

Merged
FilipB97 merged 1 commit into
masterfrom
claude/app-fixes
Jul 10, 2026
Merged

fix(app): przegląd — crash transferu, utwardzenie aktualizacji/known-hosts i poprawki bugów#182
FilipB97 merged 1 commit into
masterfrom
claude/app-fixes

Conversation

@FilipB97

Copy link
Copy Markdown
Owner

Cel

Poprawki z przeglądu aplikacji (bugi + utwardzenie bezpieczeństwa + drobny UI). Każde znalezisko było wcześniej zweryfikowane w kodzie.

⚠️ Zmian nie zbudowano lokalnie (WPF net8.0-windows nie kompiluje się na Linuksie) — walidację kompilacji i testów wykonuje CI na Windows w tym PR. Pilnuję go i naprawię, jeśli coś zgłosi.

🔴 High

  • Crash przy transferze plików — rekurencyjny upload/download/liczenie rozmiaru podążał za dowiązaniami (symlink/junction) bez limitu → pętla → StackOverflowException, którego nie łapie DispatcherUnhandledException (twardy crash, urwany transfer). Teraz: pomijamy lokalne reparse-pointy + limit głębokości (także zdalnie). Nowy klucz i18n S.sftp.toodeep (EN+PL).

🟠 Medium

  • Auto-update — adres pobrania z JSON-a wydania musi być https + host GitHuba (IsTrustedDownloadUrl). Przy niepodpisanym buildzie werdykt CurrentUnsigned jest „akceptowalny", więc to jedyna twarda kontrola pochodzenia pobieranego .exe.
  • known_hosts / ftps_certsLoad(dir, out storeUnreadable): uszkodzony magazyn ⇒ fail-closed (host/cert traktowany jak zmiana klucza, z ostrzeżeniem) zamiast cichego „nowy host?".
  • Podwójny transfer — nowy transfer nie niszczy trwającego i nie jest po cichu porzucany (guard _busy).
  • Terminal (WebView2) — nieudany init zeruje _ready, więc „Połącz ponownie" re-inicjalizuje zamiast wisieć „Connecting…" w nieskończoność.
  • Nazwy plików ze zdalnego serweraSafeCombine łapany w handlerach (status „niebezpieczna nazwa" zamiast nieobsłużonego wyjątku / cichego no-op w Release).
  • RestClientConfigureAwait(false) na wszystkich awaitach (dekodowanie dużych odpowiedzi nie blokuje wątku UI).
  • Podgląd JSON REST — kolory z palety (per-motyw), czytelne w jasnym motywie.

🟡 Low

  • RestClient: dekodowanie ciała wg charset (fallback UTF-8).
  • AtomicFile: Flush(true) przed atomowym rename (trwałość).
  • RdpUtils.SplitHostPort: IPv6 w nawiasach ([::1]:3389).
  • PasswordGen: gwarancja po ≥1 znaku z każdej wybranej klasy (Fisher-Yates).
  • RestStore: self-heal z .bak (wzorem EnvironmentStore).
  • ReachabilityService: token anuluje ConnectAsync (bez porzuconego zadania).
  • Font Mono zamiast Consolas (RestConsole); IsDefault na „Zapisz" (edytor serwera).

Świadomie odłożone (osobno)

Wyższe ryzyko bez lokalnego builda lub 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 (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

…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
@FilipB97
FilipB97 marked this pull request as ready for review July 10, 2026 21:11
@FilipB97
FilipB97 merged commit 45504f8 into master Jul 10, 2026
8 checks passed

@llamapreview llamapreview Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Loading

🌟 Strengths

  • Solid fix for the StackOverflowException crash by adding depth limits and ignoring reparse points.
  • Security hardening: IsTrustedDownloadUrl for auto-update and fail-closed behavior for corrupted known_hosts/certificates.

⚡ Key Risks & Improvements (P1)

  • src/RdpManager/XtermControl.cs: On failed WebView2 initialization, resetting _ready without completing the old TaskCompletionSource creates 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
Loading

💡 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.

Comment on lines +87 to +94
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;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants