Author SHA1 Message Date
jaap-jan e252d337d1 Take the SSH.NET release that fixes the SCP path traversal
ci / desktop nightly (pull_request) Skipped
ci / android head (pull_request) Successful in 3m26s
ci / build and test (pull_request) Successful in 2m27s
ci / api image (pull_request) Successful in 21s
Every project in the repository stopped building, on a warning about a package
none of them had changed:

    error NU1903: Warning As Error: Package 'SSH.NET' 2025.1.0 has a known high
    severity vulnerability

GHSA-q939-rpr3-3284 was published after the pin was written. ScpClient.Download
in recursive mode takes the filenames the server sends and joins them to the
local destination without validating them, so a malicious or compromised SCP
server can name "../" its way out of the download directory and write anywhere
the client process can — CWE-22, 7.1, and the same shape as OpenSSH's
CVE-2019-6111 with the traversal left in. Everything up to and including
2025.1.0 is affected; 2026.0.0 is the fix.

◆ THIS APPLICATION WAS NEVER EXPOSED, AND THE UPGRADE IS STILL THE RIGHT ANSWER.
Nothing here constructs a ScpClient. File transfer is SftpClient throughout —
SshNetSftpSession is the only path to a remote file — so the vulnerable method
has no caller to reach it from. That makes this a hygiene bump rather than an
incident, and it is worth saying plainly because the alternative on offer was a
scoped NuGetAuditSuppress with that reasoning written beside it. A suppression is
what you reach for when there is nothing to upgrade TO. There is: the maintainers
shipped the fix, taking it costs two nullable annotations, and a suppression
would have left this repository carrying a known-vulnerable library and a comment
explaining why that is fine — which stays true only until somebody adds the first
ScpClient call and has no reason to look here.

BouncyCastle.Cryptography moves 2.6.2 -> 2.7.0 as a consequence, not a
preference. CentralPackageTransitivePinningEnabled makes the PackageVersion here
the resolved version for a transitive dependency, and SSH.NET 2026.0.0 asks for
2.7.0, so a pin left at 2.6.2 is a downgrade error rather than a version this
repository gets to choose. Its comment records the constraint so the next person
does not try to walk it back.

◆ THE RELEASE NOTES SAY NO BREAKING CHANGES. THE NULLABLE ANNOTATIONS DISAGREE.
ConnectionInfo.CurrentServerEncryption is now string? — correct of them, since it
really is null until the key exchange completes — and that is CS8601 at both
places this codebase reads a cipher off a live connection. Neither can observe
the null: SshNetConnection and SshNetSftpSession are only ever constructed after
ConnectAsync has been awaited, which is what their existing remarks already say
and why the reads are at construction rather than lazy.

Coalesced to string.Empty rather than silenced with !, because empty is already
this codebase's word for a cipher that could not be read. MainWindowViewModel's
NullIfEmpty exists for exactly that, TerminalWorkspace.GetSessionFacts already
hands over string.Empty on the same grounds, and the status bar collapses on it.
So the boundary keeps the interface's non-nullable string, no consumer changes,
and an unreachable null would degrade the way a real one already does.

SshNetConnection's remark asserted that SSH.NET types the property non-nullable
and that reading straight through was therefore honest rather than lazy. That
sentence is now false, and a comment contradicting the line beneath it is worse
than no comment, so it says what is actually true: the coalesce is there for the
annotation and the pre-key-exchange window this call site cannot be in.

The Android head needed its own restore. It is outside DodoSSH.slnx, so the
solution-wide --force-evaluate does not reach it, and left alone it would have
kept the vulnerable pin and gone stale under locked mode — the hazard
platform-flags.md records, arriving on schedule.

Heeding the warning left by the ILLink commit: --force-evaluate on Windows
rewrote all 39 lock files with CRLF, and 21 of them had no content change. Only
the 18 that really moved are here.

Verified with `dotnet build` at the root, no audit-disabling flags: 0 errors,
NU1903 gone, and the 21 remaining warnings are the pre-existing Meziantou/CA and
Avalonia ones that were present in the failing build too.

A library upgrade is not a version string, so the suites that exercise it were
run against live containers rather than trusted: 87 Ssh, 80 Terminal, 27
ObjectStore, 10 Transfer, none failing.
2026-08-14 09:36:24 +02:00
26 changed files with 167 additions and 364 deletions
+13 -3
View File
@@ -88,8 +88,12 @@
--> -->
<PackageVersion Include="NSec.Cryptography" Version="26.4.0" /> <PackageVersion Include="NSec.Cryptography" Version="26.4.0" />
<PackageVersion Include="libsodium" Version="1.0.22" /> <PackageVersion Include="libsodium" Version="1.0.22" />
<!-- Managed differential oracle for the crypto test suite only. --> <!--
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.6.2" /> Managed differential oracle for the crypto test suite only. Also what SSH.NET pulls in,
so central transitive pinning makes this pin its floor too: 2.7.0 is what SSH.NET
2026.0.0 asks for, and pinning below that is a downgrade error rather than a preference.
-->
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.7.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Desktop client"> <ItemGroup Label="Desktop client">
<!-- <!--
@@ -98,8 +102,14 @@
agent forwarding (needs an upstream change; de-scoped from v1) and being handed a agent forwarding (needs an upstream change; de-scoped from v1) and being handed a
pre-connected Stream — it performs its own socket connect, which is why the relay and pre-connected Stream — it performs its own socket connect, which is why the relay and
ProxyJump both go through a loopback TCP bridge. See docs/adr/. ProxyJump both go through a loopback TCP bridge. See docs/adr/.
2026.0.0 is the fix for GHSA-q939-rpr3-3284, a path traversal in ScpClient.Download's
recursive mode that trusts server-supplied names (everything up to and including
2025.1.0 is affected). Nothing here uses ScpClient — transfers go through SftpClient —
so this was an upgrade on principle rather than an exposure, and the release notes
list no breaking changes over 2025.1.0.
--> -->
<PackageVersion Include="SSH.NET" Version="2025.1.0" /> <PackageVersion Include="SSH.NET" Version="2026.0.0" />
<!-- <!--
The S3 client, for buckets as a remote in the file browser. First-party, Apache-2.0, and The S3 client, for buckets as a remote in the file browser. First-party, Apache-2.0, and
managed only — no native assets — which is the bar this file sets for anything that gets managed only — no native assets — which is the bar this file sets for anything that gets
@@ -984,7 +984,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"NSec.Cryptography": "[26.4.0, )", "NSec.Cryptography": "[26.4.0, )",
"SSH.NET": "[2025.1.0, )" "SSH.NET": "[2026.0.0, )"
} }
}, },
"dodossh.client.storage": { "dodossh.client.storage": {
@@ -1044,9 +1044,9 @@
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"EFCore.NamingConventions": { "EFCore.NamingConventions": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -1140,11 +1140,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
} }
@@ -87,27 +87,12 @@
TextTrimming="CharacterEllipsis" /> TextTrimming="CharacterEllipsis" />
</StackPanel> </StackPanel>
<!-- <!--
Two readings stacked against the two on the left, and in the same order: what this row
IS above, how it is reached below.
◆ THE VAULT, and only when there is more than one to be in — HostRowViewModel.VaultBadge
is empty otherwise, which collapses the line and leaves the kind word centred as it was.
The palette searches every readable vault at once, so without it two hosts a team and a
person both call "prod-db" are two identical rows, and Enter takes whichever the ranking
happened to put first. Trimmed against a cap because the column is Auto-sized: a long
vault name would otherwise take its width out of the host name beside it.
credential / key / password — never the mock's SSH/SFTP kind column. Every palette credential / key / password — never the mock's SSH/SFTP kind column. Every palette
connect here is SSH, so printing that word would be a constant dressed up as a reading; connect here is SSH, so printing that word would be a constant dressed up as a reading;
see hosts-v5-design-spec.md's deviations and HostRowViewModel.Authentication. see hosts-v5-design-spec.md's deviations and HostRowViewModel.Authentication.
--> -->
<StackPanel Grid.Column="2" Spacing="1" Margin="10,0,0,0" VerticalAlignment="Center"> <TextBlock Grid.Column="2" Classes="mono qcrow-subtext" Text="{Binding Authentication}"
<TextBlock Classes="mono qcrow-subtext" Text="{Binding VaultBadge}" FontSize="10" FontSize="11" Margin="10,0,0,0" VerticalAlignment="Center" />
HorizontalAlignment="Right" MaxWidth="150"
TextTrimming="CharacterEllipsis" IsVisible="{Binding HasVaultBadge}" />
<TextBlock Classes="mono qcrow-subtext" Text="{Binding Authentication}"
FontSize="11" HorizontalAlignment="Right" />
</StackPanel>
</Grid> </Grid>
</Border> </Border>
</DataTemplate> </DataTemplate>
+8 -8
View File
@@ -405,7 +405,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"NSec.Cryptography": "[26.4.0, )", "NSec.Cryptography": "[26.4.0, )",
"SSH.NET": "[2025.1.0, )" "SSH.NET": "[2026.0.0, )"
} }
}, },
"dodossh.client.storage": { "dodossh.client.storage": {
@@ -465,9 +465,9 @@
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"EFCore.NamingConventions": { "EFCore.NamingConventions": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -567,11 +567,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
} }
@@ -55,14 +55,14 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"NSec.Cryptography": "[26.4.0, )", "NSec.Cryptography": "[26.4.0, )",
"SSH.NET": "[2025.1.0, )" "SSH.NET": "[2026.0.0, )"
} }
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"libsodium": { "libsodium": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -81,11 +81,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
} }
@@ -148,7 +148,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"NSec.Cryptography": "[26.4.0, )", "NSec.Cryptography": "[26.4.0, )",
"SSH.NET": "[2025.1.0, )" "SSH.NET": "[2026.0.0, )"
} }
}, },
"dodossh.client.storage": { "dodossh.client.storage": {
@@ -187,9 +187,9 @@
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"EFCore.NamingConventions": { "EFCore.NamingConventions": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -289,11 +289,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
} }
@@ -2141,9 +2141,8 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
/// <remarks> /// <remarks>
/// Ranked rather than merely filtered: a host whose name starts with what was typed comes before one /// Ranked rather than merely filtered: a host whose name starts with what was typed comes before one
/// that merely contains it, both come before a match found only in the address, and all three come /// that merely contains it, and both come before a match found only in the address. Typing three
/// before one found only in the vault's name. Typing three characters of a name people use daily should /// characters of a name people use daily should not put that host third.
/// not put that host third. See <see cref="Rank"/>.
/// </remarks> /// </remarks>
private void RefreshSearchResults() private void RefreshSearchResults()
{ {
@@ -2176,21 +2175,6 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
OnPropertyChanged(nameof(HasSearchResults)); OnPropertyChanged(nameof(HasSearchResults));
} }
/// <summary>Where one host places against what was typed, or <see cref="int.MaxValue"/> for no match.</summary>
/// <remarks>
/// <para>
/// <b>The vault comes last, and only when the row prints it.</b> Its name is the widest of the three
/// readings — one word can match every host in a team's vault at once — so a host whose own name or
/// address answers the query has to come first, or typing a machine's name would bury it under everybody
/// else's.
/// </para>
/// <para>
/// <see cref="HostRowViewModel.HasVaultBadge"/> rather than the name itself, so this matches exactly what
/// the row shows: a session holding one vault prints no vault on any row, and matching a name nothing
/// displays would answer a query with a list whose reason for existing is invisible — type "personal"
/// there and every host comes back for no reason anybody can see.
/// </para>
/// </remarks>
private static int Rank(HostRowViewModel host, string query) private static int Rank(HostRowViewModel host, string query)
{ {
if (host.Label.StartsWith(query, StringComparison.CurrentCultureIgnoreCase)) if (host.Label.StartsWith(query, StringComparison.CurrentCultureIgnoreCase))
@@ -2203,14 +2187,8 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
return 1; return 1;
} }
if (host.Address.Contains(query, StringComparison.CurrentCultureIgnoreCase)) return host.Address.Contains(query, StringComparison.CurrentCultureIgnoreCase)
{ ? 2
return 2;
}
return host.HasVaultBadge
&& host.VaultName.Contains(query, StringComparison.CurrentCultureIgnoreCase)
? 3
: int.MaxValue; : int.MaxValue;
} }
+8 -8
View File
@@ -207,7 +207,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"NSec.Cryptography": "[26.4.0, )", "NSec.Cryptography": "[26.4.0, )",
"SSH.NET": "[2025.1.0, )" "SSH.NET": "[2026.0.0, )"
} }
}, },
"dodossh.client.storage": { "dodossh.client.storage": {
@@ -267,9 +267,9 @@
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"EFCore.NamingConventions": { "EFCore.NamingConventions": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -369,11 +369,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
} }
@@ -304,10 +304,11 @@ internal sealed class SshNetConnection(SshClient client, HostKeyPresentation hos
/// Read at construction rather than lazily: by the time an <see cref="SshNetConnection"/> exists, /// Read at construction rather than lazily: by the time an <see cref="SshNetConnection"/> exists,
/// <see cref="SshNetConnectionFactory.ConnectAsync"/> has already awaited <c>client.ConnectAsync</c>, so /// <see cref="SshNetConnectionFactory.ConnectAsync"/> has already awaited <c>client.ConnectAsync</c>, so
/// <c>ConnectionInfo</c> is already populated and there is no earlier moment reading it would race. SSH.NET /// <c>ConnectionInfo</c> is already populated and there is no earlier moment reading it would race. SSH.NET
/// types the property as a non-nullable <c>string</c>, so this reads straight through rather than coalescing /// types the property as nullable all the same — it is null before the key exchange completes — so the
/// a null that the library's own contract says cannot occur. /// coalesce is here to satisfy that annotation, not because this call site expects to see it. Empty is the
/// value this codebase already uses for a cipher that could not be read; see <c>MainWindowViewModel.NullIfEmpty</c>.
/// </remarks> /// </remarks>
public string Cipher { get; } = client.ConnectionInfo.CurrentServerEncryption; public string Cipher { get; } = client.ConnectionInfo.CurrentServerEncryption ?? string.Empty;
/// <inheritdoc /> /// <inheritdoc />
public Task<ISshShellSession> OpenShellAsync(TerminalSize size, CancellationToken cancellationToken) public Task<ISshShellSession> OpenShellAsync(TerminalSize size, CancellationToken cancellationToken)
+3 -2
View File
@@ -24,9 +24,10 @@ internal sealed class SshNetSftpSession(SftpClient client, HostKeyPresentation h
/// <remarks> /// <remarks>
/// Read at construction, the same way and for the same reason as <c>SshNetConnection.Cipher</c>: this type /// Read at construction, the same way and for the same reason as <c>SshNetConnection.Cipher</c>: this type
/// is only ever built after <c>SshNetConnectionFactory.OpenSftpAsync</c> has awaited <c>client.ConnectAsync</c>, /// is only ever built after <c>SshNetConnectionFactory.OpenSftpAsync</c> has awaited <c>client.ConnectAsync</c>,
/// so <c>ConnectionInfo</c> is already populated by the time there is a session to read it from. /// so <c>ConnectionInfo</c> is already populated by the time there is a session to read it from. The coalesce
/// is there for SSH.NET's nullable annotation, which covers the pre-key-exchange window this never sees.
/// </remarks> /// </remarks>
public string Cipher { get; } = client.ConnectionInfo.CurrentServerEncryption; public string Cipher { get; } = client.ConnectionInfo.CurrentServerEncryption ?? string.Empty;
/// <inheritdoc /> /// <inheritdoc />
public string HomeDirectory { get; } = homeDirectory; public string HomeDirectory { get; } = homeDirectory;
+7 -7
View File
@@ -31,11 +31,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "Direct", "type": "Direct",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
}, },
@@ -54,9 +54,9 @@
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"libsodium": { "libsodium": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -37,14 +37,14 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"NSec.Cryptography": "[26.4.0, )", "NSec.Cryptography": "[26.4.0, )",
"SSH.NET": "[2025.1.0, )" "SSH.NET": "[2026.0.0, )"
} }
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"libsodium": { "libsodium": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -63,11 +63,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
} }
@@ -37,14 +37,14 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"NSec.Cryptography": "[26.4.0, )", "NSec.Cryptography": "[26.4.0, )",
"SSH.NET": "[2025.1.0, )" "SSH.NET": "[2026.0.0, )"
} }
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"libsodium": { "libsodium": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -63,11 +63,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
} }
+7 -7
View File
@@ -1744,9 +1744,9 @@
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"EFCore.NamingConventions": { "EFCore.NamingConventions": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -1847,11 +1847,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
}, },
@@ -236,51 +236,6 @@ public sealed class QuickConnectTests : IAsyncLifetime
}); });
} }
/// <remarks>
/// The palette searches every vault the session holds a key for, so a row has to say which one it came
/// out of. Two machines a team and a person both call <c>prod-db</c> are otherwise two identical rows,
/// and Enter takes whichever the ranking happened to put first.
/// <para>
/// Typed into rather than read off the unfiltered list, because the shared vault's host sorts last — the
/// active vault's rows come first — and the list virtualises, so the row this is about might never be
/// realised. Narrowing to it also proves the search reaches past the active vault at all.
/// </para>
/// </remarks>
[Fact]
public async Task AResultSaysWhichVaultItCameOutOf()
{
await SeedSharedHostAsync();
await OnThePaletteAsync((palette, window) =>
{
shell.SearchText = "prod-db";
Relayout(window);
var found = shell.SearchResults.ShouldHaveSingleItem();
found.VaultId.ShouldNotBe(session.ActiveVaultId, "the palette reaches past the active vault");
VisibleTexts(RowFor(palette, found)).ShouldContain("PLATFORM SECRETS", StringComparer.Ordinal);
});
}
/// <remarks>
/// The other half of the rule, and the reason the name is a badge rather than a column: a vault named on
/// every row of a session that has only one is the same fact repeated, which is noise rather than a
/// reading. <c>HostRowViewModel.VaultBadge</c> is empty there, and an empty line has to collapse rather
/// than leave a gap above the kind word.
/// </remarks>
[Fact]
public async Task AResultNamesNoVaultWhenThereIsOnlyOneToBeIn()
{
await OnThePaletteAsync((palette, _) =>
{
var first = shell.SearchResults[0];
first.HasVaultBadge.ShouldBeFalse("this fixture's session holds the personal vault alone");
VisibleTexts(RowFor(palette, first)).ShouldNotContain("PERSONAL", StringComparer.Ordinal);
});
}
/// <remarks> /// <remarks>
/// The palette is a box somebody is expected to start typing into, and for a while it was not: the window /// The palette is a box somebody is expected to start typing into, and for a while it was not: the window
/// focused it from the view model's <c>PropertyChanged</c>, which runs before the binding that reveals the /// focused it from the view model's <c>PropertyChanged</c>, which runs before the binding that reveals the
@@ -352,25 +307,6 @@ public sealed class QuickConnectTests : IAsyncLifetime
.OfType<ListBoxItem>() .OfType<ListBoxItem>()
.First(item => ReferenceEquals(item.DataContext, host)); .First(item => ReferenceEquals(item.DataContext, host));
/// <summary>What one row actually draws, in order, ignoring the lines that collapsed.</summary>
private static List<string> VisibleTexts(Visual row) =>
row.GetVisualDescendants()
.OfType<TextBlock>()
.Where(text => text.IsEffectivelyVisible)
.Select(text => text.Text ?? string.Empty)
.ToList();
/// <summary>Runs the layout pass the application's dispatcher would run after the list changed.</summary>
/// <remarks>
/// Without it the new rows are in the collection but not in the visual tree, so <see cref="RowFor"/>
/// finds nothing to look at.
/// </remarks>
private static void Relayout(Window window)
{
Dispatcher.UIThread.RunJobs();
window.UpdateLayout();
}
private static Point Centre(Visual control, Visual window) => private static Point Centre(Visual control, Visual window) =>
control.TranslatePoint(new Point(control.Bounds.Width / 2, control.Bounds.Height / 2), window) control.TranslatePoint(new Point(control.Bounds.Width / 2, control.Bounds.Height / 2), window)
?? throw new InvalidOperationException("the control is not in this window's tree"); ?? throw new InvalidOperationException("the control is not in this window's tree");
@@ -389,37 +325,4 @@ public sealed class QuickConnectTests : IAsyncLifetime
await vault.LoadAsync(Token); await vault.LoadAsync(Token);
} }
/// <summary>
/// Adds a second, shared vault to the fixture's session and files one host into it.
/// </summary>
/// <remarks>
/// Straight into the session rather than through <c>VaultsViewModel</c>, which is the technique the
/// settings pages' suite uses and for the same reason: creating a vault needs a connection, and this
/// shell has none. The key is generated on this machine either way, so what the session ends up holding
/// is the same thing a real creation leaves behind — see <see cref="StubTeamServer"/>.
/// </remarks>
private async Task SeedSharedHostAsync()
{
using var teamServer = new StubTeamServer();
var shared = await session.CreateTeamVaultAsync(
teamServer.Teams, StubTeamServer.SharedTeamId, "Platform secrets", Token);
await vault.LoadAsync(Token);
vault.SelectedTargetVault =
vault.TargetVaults.Single(choice => choice.VaultId == shared.VaultId);
vault.NewHostCommand.Execute(null);
vault.EditorLabel = "prod-db";
vault.EditorHostname = "db.internal";
vault.EditorUsername = "deploy";
await vault.SaveHostCommand.ExecuteAsync(null);
vault.IsEditing.ShouldBeFalse(vault.Status);
await vault.LoadAsync(Token);
}
} }
@@ -551,7 +551,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"NSec.Cryptography": "[26.4.0, )", "NSec.Cryptography": "[26.4.0, )",
"SSH.NET": "[2025.1.0, )" "SSH.NET": "[2026.0.0, )"
} }
}, },
"dodossh.client.storage": { "dodossh.client.storage": {
@@ -663,9 +663,9 @@
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"CommunityToolkit.Mvvm": { "CommunityToolkit.Mvvm": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -771,11 +771,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
}, },
@@ -1765,28 +1765,6 @@ public sealed class ShellFlowTests : IAsyncLifetime
shell.IsTerminalShowing.ShouldBeTrue(); shell.IsTerminalShowing.ShouldBeTrue();
} }
/// <remarks>
/// The palette searches a host's vault name, and only where the row prints it — which a session holding
/// one vault never does. Matching it anyway would answer "personal" with the entire keychain, ranked
/// behind nothing and explained by nothing on screen. See <c>MainWindowViewModel.Rank</c>.
/// </remarks>
[Fact]
public async Task ThePalette_DoesNotMatchTheVaultNameWhenThereIsOnlyOneVaultToBeIn()
{
var vault = await ReadyToConnectAsync();
vault.VaultName.ShouldBe("Personal", "which is the word this test types");
shell.ToggleSearchCommand.Execute(null);
shell.SearchResults.ShouldHaveSingleItem()
.HasVaultBadge.ShouldBeFalse("one vault is nothing to tell apart");
shell.SearchText = "personal";
shell.SearchResults.ShouldBeEmpty();
}
/// <remarks> /// <remarks>
/// The rail marks where you are, and a terminal is not one of its destinations. Lighting HOSTS while a /// The rail marks where you are, and a terminal is not one of its destinations. Lighting HOSTS while a
/// terminal fills the window would point at a screen that is not showing — and the selected tab already /// terminal fills the window would point at a screen that is not showing — and the selected tab already
@@ -766,59 +766,6 @@ public sealed class VaultSharingTests : IAsyncLifetime
vault.Status.ShouldContain("left alone", Case.Insensitive); vault.Status.ShouldContain("left alone", Case.Insensitive);
} }
/// <remarks>
/// <para>
/// The palette reaches every vault the session holds a key for, so the vault's name is one of the things
/// somebody can reasonably type into it: "show me the team's machines" is a question a list spanning two
/// vaults invites, and until now the only answer was to know one of the machines by name already.
/// </para>
/// <para>
/// The order is the assertion that matters. A vault name is the widest of the three readings — one word
/// matches every host in that vault at once — so the host whose own name carries the word has to come
/// first, or typing a machine's name would bury it under everybody else's.
/// </para>
/// </remarks>
[Fact]
public async Task ThePalette_FindsAHostByItsVaultsName_BehindOneCalledThatItself()
{
await UnlockedAsync();
var vaults = shell.Vaults;
await CreateVaultAsync(vaults, "Platform secrets");
var vault = shell.Vault!;
var sharedVaultId = vaults.SelectedVault!.VaultId;
await vault.LoadAsync(Token);
vault.NewHostCommand.Execute(null);
vault.EditorLabel = "platform-gateway";
vault.EditorHostname = "gateway.internal";
await vault.SaveHostCommand.ExecuteAsync(null);
vault.NewHostCommand.Execute(null);
vault.EditorSelectedVault =
vault.EditorVaultChoices.Single(choice => choice.VaultId == sharedVaultId);
vault.EditorLabel = "prod-db";
vault.EditorHostname = "db.internal";
await vault.SaveHostCommand.ExecuteAsync(null);
vault.IsEditing.ShouldBeFalse(vault.Status);
shell.ToggleSearchCommand.Execute(null);
shell.SearchText = "platform";
shell.SearchResults
.Select(row => row.Label)
.ShouldBe(["platform-gateway", "prod-db"], "a host's own name outranks its vault's");
shell.SearchResults[1].VaultBadge.ShouldBe(
"PLATFORM SECRETS", "and the row says what it was found by");
}
/// <summary> /// <summary>
/// Two hosts for the run above: one in the personal vault under a group, one already in the destination. /// Two hosts for the run above: one in the personal vault under a group, one already in the destination.
/// </summary> /// </summary>
@@ -540,7 +540,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"NSec.Cryptography": "[26.4.0, )", "NSec.Cryptography": "[26.4.0, )",
"SSH.NET": "[2025.1.0, )" "SSH.NET": "[2026.0.0, )"
} }
}, },
"dodossh.client.storage": { "dodossh.client.storage": {
@@ -652,9 +652,9 @@
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"CommunityToolkit.Mvvm": { "CommunityToolkit.Mvvm": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -760,11 +760,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
}, },
@@ -229,7 +229,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"NSec.Cryptography": "[26.4.0, )", "NSec.Cryptography": "[26.4.0, )",
"SSH.NET": "[2025.1.0, )" "SSH.NET": "[2026.0.0, )"
} }
}, },
"AWSSDK.Core": { "AWSSDK.Core": {
@@ -249,9 +249,9 @@
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"libsodium": { "libsodium": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -270,11 +270,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
} }
@@ -340,7 +340,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"NSec.Cryptography": "[26.4.0, )", "NSec.Cryptography": "[26.4.0, )",
"SSH.NET": "[2025.1.0, )" "SSH.NET": "[2026.0.0, )"
} }
}, },
"dodossh.client.storage": { "dodossh.client.storage": {
@@ -379,9 +379,9 @@
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"EFCore.NamingConventions": { "EFCore.NamingConventions": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -481,11 +481,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
} }
@@ -41,11 +41,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "Direct", "type": "Direct",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
}, },
@@ -306,7 +306,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"NSec.Cryptography": "[26.4.0, )", "NSec.Cryptography": "[26.4.0, )",
"SSH.NET": "[2025.1.0, )" "SSH.NET": "[2026.0.0, )"
} }
}, },
"dodossh.client.terminal": { "dodossh.client.terminal": {
@@ -317,9 +317,9 @@
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"libsodium": { "libsodium": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -217,7 +217,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"NSec.Cryptography": "[26.4.0, )", "NSec.Cryptography": "[26.4.0, )",
"SSH.NET": "[2025.1.0, )" "SSH.NET": "[2026.0.0, )"
} }
}, },
"dodossh.client.terminal": { "dodossh.client.terminal": {
@@ -228,9 +228,9 @@
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"libsodium": { "libsodium": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -249,11 +249,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
} }
@@ -217,7 +217,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"NSec.Cryptography": "[26.4.0, )", "NSec.Cryptography": "[26.4.0, )",
"SSH.NET": "[2025.1.0, )" "SSH.NET": "[2026.0.0, )"
} }
}, },
"dodossh.client.transfer": { "dodossh.client.transfer": {
@@ -228,9 +228,9 @@
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"libsodium": { "libsodium": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -249,11 +249,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
} }
@@ -390,9 +390,9 @@
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"EFCore.NamingConventions": { "EFCore.NamingConventions": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -431,11 +431,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
}, },
+8 -8
View File
@@ -436,7 +436,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"NSec.Cryptography": "[26.4.0, )", "NSec.Cryptography": "[26.4.0, )",
"SSH.NET": "[2025.1.0, )" "SSH.NET": "[2026.0.0, )"
} }
}, },
"dodossh.client.storage": { "dodossh.client.storage": {
@@ -486,9 +486,9 @@
}, },
"BouncyCastle.Cryptography": { "BouncyCastle.Cryptography": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2.6.2, )", "requested": "[2.7.0, )",
"resolved": "2.6.2", "resolved": "2.7.0",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w==" "contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
}, },
"EFCore.NamingConventions": { "EFCore.NamingConventions": {
"type": "CentralTransitive", "type": "CentralTransitive",
@@ -599,11 +599,11 @@
}, },
"SSH.NET": { "SSH.NET": {
"type": "CentralTransitive", "type": "CentralTransitive",
"requested": "[2025.1.0, )", "requested": "[2026.0.0, )",
"resolved": "2025.1.0", "resolved": "2026.0.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==", "contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": { "dependencies": {
"BouncyCastle.Cryptography": "2.6.2", "BouncyCastle.Cryptography": "2.7.0",
"Microsoft.Extensions.Logging.Abstractions": "8.0.3" "Microsoft.Extensions.Logging.Abstractions": "8.0.3"
} }
} }