From 7e5b32f6226823a204ca41a97bbaa6eeef97f605 Mon Sep 17 00:00:00 2001 From: Zach Steindler Date: Mon, 29 Jun 2026 11:19:08 -0400 Subject: [PATCH 1/3] Adding support to Runner to communicate via vsock --- src/Runner.Common/HostContext.cs | 2 +- .../RunnerSecretRegistrationNotifier.cs | 158 ++++++++++++++++++ .../Logging/ISecretRegistrationNotifier.cs | 23 +++ src/Sdk/DTLogging/Logging/SecretMasker.cs | 24 +++ 4 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 src/Runner.Common/RunnerSecretRegistrationNotifier.cs create mode 100644 src/Sdk/DTLogging/Logging/ISecretRegistrationNotifier.cs diff --git a/src/Runner.Common/HostContext.cs b/src/Runner.Common/HostContext.cs index ffb08684a53..2df100dae3e 100644 --- a/src/Runner.Common/HostContext.cs +++ b/src/Runner.Common/HostContext.cs @@ -60,7 +60,7 @@ public sealed class HostContext : EventListener, IObserver, private static int[] _vssHttpCredentialEventIds = new int[] { 11, 13, 14, 15, 16, 17, 18, 20, 21, 22, 27, 29 }; private readonly ConcurrentDictionary _serviceInstances = new(); private readonly ConcurrentDictionary _serviceTypes = new(); - private readonly ISecretMasker _secretMasker = new SecretMasker(); + private readonly ISecretMasker _secretMasker = new SecretMasker(new RunnerSecretRegistrationNotifier()); private readonly List _userAgents = new() { new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version) }; private CancellationTokenSource _runnerShutdownTokenSource = new(); private object _perfLock = new(); diff --git a/src/Runner.Common/RunnerSecretRegistrationNotifier.cs b/src/Runner.Common/RunnerSecretRegistrationNotifier.cs new file mode 100644 index 00000000000..463949db1c7 --- /dev/null +++ b/src/Runner.Common/RunnerSecretRegistrationNotifier.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Win32.SafeHandles; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Json; +using GitHub.DistributedTask.Logging; + +namespace GitHub.Runner.Common +{ + public sealed class RunnerSecretRegistrationNotifier : ISecretRegistrationNotifier + { + private const AddressFamily VsockAddressFamily = (AddressFamily)40; + internal bool IsLinux { get; } + private readonly Socket _vsock; + private readonly object _vsockSendLock = new object(); + private static readonly object _debugFileLock = new object(); + + public RunnerSecretRegistrationNotifier() + { + IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + _vsock = IsLinux ? TryCreateConnectedVsock() : null; + } + + public void NotifySecretRegistration(List secrets, List secretRegexes) + { + if (_vsock == null) + { + return; + } + + try + { + string jsonPayload = JsonSerializer.Serialize(new + { + RunnerSecrets = new + { + secrets = secrets ?? new List(), + secretRegexes = secretRegexes ?? new List() + } + }); + + byte[] payloadBytes = Encoding.UTF8.GetBytes(jsonPayload); + byte[] lengthPrefix = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(payloadBytes.Length)); + + lock (_vsockSendLock) + { + SendAll(_vsock, lengthPrefix); + SendAll(_vsock, payloadBytes); + } + } + catch + { + // Notification delivery is best-effort and must never break secret masking. + } + } + + private static Socket TryCreateConnectedVsock() + { + Socket socket = null; + try + { + SafeSocketHandle nativeSocket = NativeSocket((int)VsockAddressFamily, (int)SocketType.Stream, 0); + if (nativeSocket.IsInvalid) + { + int error = Marshal.GetLastPInvokeError(); + nativeSocket.Dispose(); + throw new SocketException(error); + } + + socket = new Socket(nativeSocket); + socket.Connect(new HostVsockEndPoint(2, 9999)); + return socket; + } + catch (SocketException ex) + { + socket?.Dispose(); + return null; + } + catch (Exception ex) + { + socket?.Dispose(); + return null; + } + } + + [DllImport("libc", SetLastError = true, EntryPoint = "socket")] + private static extern SafeSocketHandle NativeSocket(int domain, int type, int protocol); + + private static void SendAll(Socket socket, byte[] buffer) + { + int offset = 0; + while (offset < buffer.Length) + { + int bytesSent = socket.Send(buffer, offset, buffer.Length - offset, SocketFlags.None); + if (bytesSent <= 0) + { + throw new SocketException((int)SocketError.ConnectionReset); + } + + offset += bytesSent; + } + } + + private sealed class HostVsockEndPoint : EndPoint + { + private const int SocketAddressSize = 16; + private readonly uint _cid; + private readonly uint _port; + + public HostVsockEndPoint(uint cid, uint port) + { + _cid = cid; + _port = port; + } + + public override AddressFamily AddressFamily => VsockAddressFamily; + + public override SocketAddress Serialize() + { + SocketAddress socketAddress = new SocketAddress(AddressFamily.Unspecified, SocketAddressSize); + // sockaddr_vm layout: family(0-1), reserved1(2-3), port(4-7), cid(8-11) + ushort family = (ushort)AddressFamily; + socketAddress[0] = (byte)(family & 0xFF); + socketAddress[1] = (byte)((family >> 8) & 0xFF); + socketAddress[2] = 0; + socketAddress[3] = 0; + socketAddress[4] = (byte)(_port & 0xFF); + socketAddress[5] = (byte)((_port >> 8) & 0xFF); + socketAddress[6] = (byte)((_port >> 16) & 0xFF); + socketAddress[7] = (byte)((_port >> 24) & 0xFF); + socketAddress[8] = (byte)(_cid & 0xFF); + socketAddress[9] = (byte)((_cid >> 8) & 0xFF); + socketAddress[10] = (byte)((_cid >> 16) & 0xFF); + socketAddress[11] = (byte)((_cid >> 24) & 0xFF); + return socketAddress; + } + + public override EndPoint Create(SocketAddress socketAddress) + { + uint port = (uint)socketAddress[4] + | ((uint)socketAddress[5] << 8) + | ((uint)socketAddress[6] << 16) + | ((uint)socketAddress[7] << 24); + + uint cid = (uint)socketAddress[8] + | ((uint)socketAddress[9] << 8) + | ((uint)socketAddress[10] << 16) + | ((uint)socketAddress[11] << 24); + + return new HostVsockEndPoint(cid, port); + } + } + } +} diff --git a/src/Sdk/DTLogging/Logging/ISecretRegistrationNotifier.cs b/src/Sdk/DTLogging/Logging/ISecretRegistrationNotifier.cs new file mode 100644 index 00000000000..1a588d17ae7 --- /dev/null +++ b/src/Sdk/DTLogging/Logging/ISecretRegistrationNotifier.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace GitHub.DistributedTask.Logging +{ + public interface ISecretRegistrationNotifier + { + void NotifySecretRegistration(List secretValues, List secretRegexes); + } + + public sealed class NoOpSecretRegistrationNotifier : ISecretRegistrationNotifier + { + public static readonly NoOpSecretRegistrationNotifier Instance = new NoOpSecretRegistrationNotifier(); + + private NoOpSecretRegistrationNotifier() + { + } + + public void NotifySecretRegistration(List secretValues, List secretRegexes) + { + // Intentionally empty. A concrete API caller can be wired later. + } + } +} diff --git a/src/Sdk/DTLogging/Logging/SecretMasker.cs b/src/Sdk/DTLogging/Logging/SecretMasker.cs index 430b977f521..5f53216f7f6 100644 --- a/src/Sdk/DTLogging/Logging/SecretMasker.cs +++ b/src/Sdk/DTLogging/Logging/SecretMasker.cs @@ -11,11 +11,17 @@ namespace GitHub.DistributedTask.Logging public sealed class SecretMasker : ISecretMasker, IDisposable { public SecretMasker() + : this(NoOpSecretRegistrationNotifier.Instance) + { + } + + public SecretMasker(ISecretRegistrationNotifier secretRegistrationNotifier) { m_originalValueSecrets = new HashSet(); m_regexSecrets = new HashSet(); m_valueEncoders = new HashSet(); m_valueSecrets = new HashSet(); + m_secretRegistrationNotifier = secretRegistrationNotifier ?? NoOpSecretRegistrationNotifier.Instance; } private SecretMasker(SecretMasker copy) @@ -30,6 +36,7 @@ private SecretMasker(SecretMasker copy) m_regexSecrets = new HashSet(copy.m_regexSecrets); m_valueEncoders = new HashSet(copy.m_valueEncoders); m_valueSecrets = new HashSet(copy.m_valueSecrets); + m_secretRegistrationNotifier = copy.m_secretRegistrationNotifier; } finally { @@ -66,6 +73,8 @@ public void AddRegex(String pattern) m_lock.ExitWriteLock(); } } + + NotifySecretRegistration(new List(), new List { pattern }); } /// @@ -133,6 +142,8 @@ public void AddValue(String value) m_lock.ExitWriteLock(); } } + + NotifySecretRegistration(new List { value }, new List()); } /// @@ -293,6 +304,19 @@ public String MaskSecrets(String input) private readonly HashSet m_regexSecrets; private readonly HashSet m_valueEncoders; private readonly HashSet m_valueSecrets; + private readonly ISecretRegistrationNotifier m_secretRegistrationNotifier; private ReaderWriterLockSlim m_lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + + private void NotifySecretRegistration(List secretValues, List secretRegexes) + { + try + { + m_secretRegistrationNotifier.NotifySecretRegistration(secretValues, secretRegexes); + } + catch + { + // Notification failures must never break masking behavior. + } + } } } From 92168447c7f33236d8b98720f7bce38ffffdb078 Mon Sep 17 00:00:00 2001 From: Zach Steindler Date: Wed, 1 Jul 2026 15:31:02 -0400 Subject: [PATCH 2/3] Fix exception handling --- src/Runner.Common/RunnerSecretRegistrationNotifier.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Runner.Common/RunnerSecretRegistrationNotifier.cs b/src/Runner.Common/RunnerSecretRegistrationNotifier.cs index 463949db1c7..1d9dcc8fad9 100644 --- a/src/Runner.Common/RunnerSecretRegistrationNotifier.cs +++ b/src/Runner.Common/RunnerSecretRegistrationNotifier.cs @@ -75,12 +75,7 @@ private static Socket TryCreateConnectedVsock() socket.Connect(new HostVsockEndPoint(2, 9999)); return socket; } - catch (SocketException ex) - { - socket?.Dispose(); - return null; - } - catch (Exception ex) + catch { socket?.Dispose(); return null; From 0b388ba8cc56ab1c482df0fbacf7a62f4813efaa Mon Sep 17 00:00:00 2001 From: Zach Steindler Date: Wed, 1 Jul 2026 16:29:28 -0400 Subject: [PATCH 3/3] Remove unused items --- src/Runner.Common/RunnerSecretRegistrationNotifier.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Runner.Common/RunnerSecretRegistrationNotifier.cs b/src/Runner.Common/RunnerSecretRegistrationNotifier.cs index 1d9dcc8fad9..5ce48ada5ba 100644 --- a/src/Runner.Common/RunnerSecretRegistrationNotifier.cs +++ b/src/Runner.Common/RunnerSecretRegistrationNotifier.cs @@ -1,13 +1,11 @@ using System; using System.Collections.Generic; -using System.IO; using Microsoft.Win32.SafeHandles; using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text; using System.Text.Json; -using GitHub.DistributedTask.Logging; namespace GitHub.Runner.Common { @@ -17,7 +15,6 @@ public sealed class RunnerSecretRegistrationNotifier : ISecretRegistrationNotifi internal bool IsLinux { get; } private readonly Socket _vsock; private readonly object _vsockSendLock = new object(); - private static readonly object _debugFileLock = new object(); public RunnerSecretRegistrationNotifier() {