Skip to content

Commit 499f3ba

Browse files
authored
Warn on WinGet upgrades when installer host changes (#4718)
1 parent c3908e5 commit 499f3ba

12 files changed

Lines changed: 402 additions & 68 deletions

File tree

Lines changed: 58 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,61 @@
11
{
2-
"af": "100%",
3-
"ar": "100%",
4-
"be": "100%",
5-
"bg": "100%",
6-
"bn": "100%",
7-
"da": "100%",
8-
"el": "100%",
9-
"eo": "100%",
10-
"es": "100%",
11-
"es-MX": "100%",
12-
"et": "100%",
13-
"fa": "100%",
14-
"fil": "100%",
15-
"gl": "100%",
16-
"gu": "100%",
17-
"he": "100%",
18-
"hi": "100%",
19-
"hr": "100%",
20-
"hu": "100%",
21-
"id": "100%",
22-
"ja": "100%",
23-
"ka": "100%",
24-
"kn": "100%",
25-
"ko": "100%",
26-
"ku": "100%",
27-
"lt": "100%",
28-
"mk": "100%",
29-
"mr": "100%",
30-
"nb": "100%",
31-
"nn": "100%",
32-
"pt_PT": "100%",
33-
"ru": "100%",
34-
"sa": "100%",
35-
"si": "100%",
36-
"sk": "100%",
37-
"sl": "100%",
38-
"sr": "100%",
39-
"ta": "100%",
40-
"tl": "100%",
41-
"th": "100%",
42-
"tr": "100%",
43-
"uk": "100%",
44-
"ur": "100%",
45-
"ca": "100%",
46-
"cs": "100%",
47-
"de": "100%",
48-
"fi": "100%",
49-
"fr": "100%",
50-
"it": "100%",
51-
"nl": "100%",
52-
"pl": "100%",
53-
"pt_BR": "100%",
54-
"ro": "100%",
55-
"sq": "100%",
56-
"sv": "100%",
57-
"vi": "100%",
58-
"zh_CN": "100%",
59-
"zh_TW": "100%",
2+
"af": "99%",
3+
"ar": "99%",
4+
"be": "99%",
5+
"bg": "99%",
6+
"bn": "99%",
7+
"da": "99%",
8+
"el": "99%",
9+
"eo": "99%",
10+
"es": "99%",
11+
"es-MX": "99%",
12+
"et": "99%",
13+
"fa": "99%",
14+
"fil": "99%",
15+
"gl": "99%",
16+
"gu": "99%",
17+
"he": "99%",
18+
"hi": "99%",
19+
"hr": "99%",
20+
"hu": "99%",
21+
"id": "99%",
22+
"ja": "99%",
23+
"ka": "99%",
24+
"kn": "99%",
25+
"ko": "99%",
26+
"ku": "99%",
27+
"lt": "99%",
28+
"mk": "99%",
29+
"mr": "99%",
30+
"nb": "99%",
31+
"nn": "99%",
32+
"pt_PT": "99%",
33+
"ru": "99%",
34+
"sa": "99%",
35+
"si": "99%",
36+
"sk": "99%",
37+
"sl": "99%",
38+
"sr": "99%",
39+
"ta": "99%",
40+
"tl": "99%",
41+
"th": "99%",
42+
"tr": "99%",
43+
"uk": "99%",
44+
"ur": "99%",
45+
"ca": "99%",
46+
"cs": "99%",
47+
"de": "99%",
48+
"fi": "99%",
49+
"fr": "99%",
50+
"it": "99%",
51+
"nl": "99%",
52+
"pl": "99%",
53+
"pt_BR": "99%",
54+
"ro": "99%",
55+
"sq": "99%",
56+
"sv": "99%",
57+
"vi": "99%",
58+
"zh_CN": "99%",
59+
"zh_TW": "99%",
6060
"en": "100%"
6161
}

‎src/Languages/lang_en.json‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -808,5 +808,7 @@
808808
"Error": "Error",
809809
"Log in failed: ": "Log in failed: ",
810810
"Log out failed: ": "Log out failed: ",
811-
"Package backup settings": "Package backup settings"
811+
"Package backup settings": "Package backup settings",
812+
"Warn me when the installer URL host changes between the installed version and the new version (WinGet only)": "Warn me when the installer URL host changes between the installed version and the new version (WinGet only)",
813+
"Installer host changed since the installed version.\nOld: {0}\nNew: {1}\n\nThis is usually harmless (the publisher moved hosting), but can also indicate a hijacked package manifest. Verify the new source before upgrading.": "Installer host changed since the installed version.\nOld: {0}\nNew: {1}\n\nThis is usually harmless (the publisher moved hosting), but can also indicate a hijacked package manifest. Verify the new source before upgrading."
812814
}

‎src/UniGetUI.Avalonia/Models/PackageCollections.cs‎

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66
using Avalonia.Media.Imaging;
77
using Avalonia.Threading;
88
using UniGetUI.Avalonia.ViewModels.Pages;
9+
using UniGetUI.Core.Logging;
910
using UniGetUI.Core.SettingsEngine;
1011
using UniGetUI.Core.Tools;
1112
using UniGetUI.Interface.Enums;
1213
using UniGetUI.PackageEngine.Interfaces;
14+
#if WINDOWS
15+
using UniGetUI.PackageEngine.Managers.WingetManager;
16+
#endif
1317

1418
// ReSharper disable once CheckNamespace
1519
namespace UniGetUI.PackageEngine.PackageClasses;
@@ -64,6 +68,11 @@ public bool IsChecked
6468
public string TagIconPath { get; private set; } = "";
6569
public bool TagIconVisible { get; private set; }
6670

71+
public bool InstallerHostChanged { get; private set; }
72+
public string InstallerHostChangeTooltip { get; private set; } = "";
73+
74+
private CancellationTokenSource? _installerHostCheckCts;
75+
6776
public string SourceIconPath => IconTypeToSvgPath(Package.Source.IconId);
6877

6978
private static string IconTypeToSvgPath(IconType icon)
@@ -94,6 +103,81 @@ public PackageWrapper(IPackage package, PackagesPageViewModel page)
94103

95104
if (!Settings.Get(Settings.K.DisableIconsOnPackageLists))
96105
_ = LoadIconAsync();
106+
107+
MaybeStartInstallerHostCheck();
108+
}
109+
110+
/// <summary>
111+
/// For upgradable WinGet packages, asynchronously fetches the installer URL host for
112+
/// both the installed and the new version, and flags the row when the hosts differ.
113+
/// See issue #4617 — defense-in-depth signal that an upgrade may be redirecting the
114+
/// download to a different domain than the user originally trusted.
115+
/// </summary>
116+
private void MaybeStartInstallerHostCheck()
117+
{
118+
#if WINDOWS
119+
if (!Package.IsUpgradable) return;
120+
if (Package.Manager is not WinGet) return;
121+
if (Settings.Get(Settings.K.DisableInstallerHostChangeWarning)) return;
122+
123+
string installedVersion = Package.VersionString;
124+
string newVersion = Package.NewVersionString;
125+
if (string.IsNullOrWhiteSpace(installedVersion) || string.IsNullOrWhiteSpace(newVersion))
126+
return;
127+
if (installedVersion == newVersion) return;
128+
129+
_installerHostCheckCts?.Cancel();
130+
_installerHostCheckCts = new CancellationTokenSource();
131+
CancellationToken token = _installerHostCheckCts.Token;
132+
133+
_ = Task.Run(async () =>
134+
{
135+
try
136+
{
137+
if (token.IsCancellationRequested) return;
138+
var oldHosts = WinGet.TryGetInstallerHostsForVersion(Package, installedVersion);
139+
if (token.IsCancellationRequested) return;
140+
var newHosts = WinGet.TryGetInstallerHostsForVersion(Package, newVersion);
141+
if (token.IsCancellationRequested) return;
142+
143+
if (oldHosts is null || newHosts is null) return;
144+
// Only flag when the two host sets are fully disjoint. If they share even
145+
// one host, the publisher hasn't moved hosting — adding/removing CDN mirrors
146+
// or architectures shouldn't trigger the warning.
147+
if (oldHosts.Overlaps(newHosts)) return;
148+
149+
string tooltip = CoreTools.Translate(
150+
"Installer host changed since the installed version.\n"
151+
+ "Old: {0}\n"
152+
+ "New: {1}\n\n"
153+
+ "This is usually harmless (the publisher moved hosting), "
154+
+ "but can also indicate a hijacked package manifest. "
155+
+ "Verify the new source before upgrading.",
156+
string.Join(", ", oldHosts),
157+
string.Join(", ", newHosts)
158+
);
159+
160+
await Dispatcher.UIThread.InvokeAsync(() =>
161+
{
162+
if (token.IsCancellationRequested) return;
163+
InstallerHostChanged = true;
164+
InstallerHostChangeTooltip = tooltip;
165+
PropertyChanged?.Invoke(
166+
this,
167+
new PropertyChangedEventArgs(nameof(InstallerHostChanged))
168+
);
169+
PropertyChanged?.Invoke(
170+
this,
171+
new PropertyChangedEventArgs(nameof(InstallerHostChangeTooltip))
172+
);
173+
});
174+
}
175+
catch (Exception ex)
176+
{
177+
Logger.Warn($"Installer-host check failed for {Package.Id}: {ex.Message}");
178+
}
179+
}, token);
180+
#endif
97181
}
98182

99183
private async Task LoadIconAsync()
@@ -187,6 +271,9 @@ private void UpdateDisplayState()
187271
public void Dispose()
188272
{
189273
Package.PropertyChanged -= Package_PropertyChanged;
274+
_installerHostCheckCts?.Cancel();
275+
_installerHostCheckCts?.Dispose();
276+
_installerHostCheckCts = null;
190277
}
191278
}
192279

‎src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Updates.axaml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@
8181
BorderThickness="1,0,1,1"
8282
CornerRadius="0,0,8,8"/>
8383

84+
<settings:CheckboxCard SettingName="DisableInstallerHostChangeWarning"
85+
Text="{t:Translate Text='Warn me when the installer URL host changes between the installed version and the new version (WinGet only)'}"
86+
CornerRadius="8"
87+
Margin="0,8,0,0"/>
88+
8489
<ContentControl x:Name="ReleaseDateCompatTableHolder"
8590
Margin="0,8,0,0"/>
8691

‎src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,13 @@
469469
<DataTemplate x:DataType="pkg:PackageWrapper">
470470
<StackPanel Orientation="Horizontal" Spacing="6" VerticalAlignment="Center" Margin="4,0">
471471
<controls:SvgIcon Path="avares://UniGetUI.Avalonia/Assets/Symbols/download.svg"
472-
Width="16" Height="16" VerticalAlignment="Center"/>
472+
Width="16" Height="16" VerticalAlignment="Center"
473+
IsVisible="{Binding !InstallerHostChanged}"/>
474+
<controls:SvgIcon Path="avares://UniGetUI.Avalonia/Assets/Symbols/warning_filled.svg"
475+
Width="16" Height="16" VerticalAlignment="Center"
476+
Foreground="#F59E0B"
477+
IsVisible="{Binding InstallerHostChanged}"
478+
ToolTip.Tip="{Binding InstallerHostChangeTooltip}"/>
473479
<TextBlock Text="{Binding Package.NewVersionString}" VerticalAlignment="Center"/>
474480
</StackPanel>
475481
</DataTemplate>
@@ -528,6 +534,13 @@
528534
IsVisible="{Binding TagIconVisible}"
529535
HorizontalAlignment="Right"
530536
VerticalAlignment="Bottom"/>
537+
<controls:SvgIcon Path="avares://UniGetUI.Avalonia/Assets/Symbols/warning_filled.svg"
538+
Width="14" Height="14"
539+
Foreground="#F59E0B"
540+
IsVisible="{Binding InstallerHostChanged}"
541+
ToolTip.Tip="{Binding InstallerHostChangeTooltip}"
542+
HorizontalAlignment="Right"
543+
VerticalAlignment="Top"/>
531544
</Panel>
532545
<StackPanel Grid.Column="1" VerticalAlignment="Center" Spacing="1">
533546
<TextBlock Text="{Binding Package.Name}"
@@ -604,6 +617,13 @@
604617
IsVisible="{Binding TagIconVisible}"
605618
HorizontalAlignment="Right"
606619
VerticalAlignment="Bottom"/>
620+
<controls:SvgIcon Path="avares://UniGetUI.Avalonia/Assets/Symbols/warning_filled.svg"
621+
Width="18" Height="18"
622+
Foreground="#F59E0B"
623+
IsVisible="{Binding InstallerHostChanged}"
624+
ToolTip.Tip="{Binding InstallerHostChangeTooltip}"
625+
HorizontalAlignment="Right"
626+
VerticalAlignment="Top"/>
607627
</Panel>
608628
<TextBlock Grid.Row="2"
609629
Text="{Binding Package.Name}"

‎src/UniGetUI.Core.Settings/SettingsEngine_Names.cs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public enum K
9090
WinGetCliToolPreference,
9191
WinGetComApiPolicy,
9292
DisableClassicMode,
93+
DisableInstallerHostChangeWarning,
9394

9495
Test1,
9596
Test2,
@@ -191,6 +192,7 @@ public static string ResolveKey(K key)
191192
K.WinGetCliToolPreference => "WinGetCliToolPreference",
192193
K.WinGetComApiPolicy => "WinGetComApiPolicy",
193194
K.DisableClassicMode => "DisableClassicMode",
195+
K.DisableInstallerHostChangeWarning => "DisableInstallerHostChangeWarning",
194196

195197
K.Test1 => "TestSetting1",
196198
K.Test2 => "TestSetting2",

0 commit comments

Comments
 (0)