From 870b8a489fcb75fa95f6255dac787f932a70ba84 Mon Sep 17 00:00:00 2001 From: Zach Steindler Date: Mon, 29 Jun 2026 11:19:08 -0400 Subject: [PATCH 1/4] 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 feda14cf31017a8eb98ffb8a5fd7c2553f5712cc Mon Sep 17 00:00:00 2001 From: Zach Steindler Date: Wed, 1 Jul 2026 15:31:02 -0400 Subject: [PATCH 2/4] 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 2e9e62275620de84744a324728e46341567eec83 Mon Sep 17 00:00:00 2001 From: Zach Steindler Date: Wed, 1 Jul 2026 16:29:28 -0400 Subject: [PATCH 3/4] 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() { From c782d0d6778568850f1db312a5d7fe3bd7c6f26a Mon Sep 17 00:00:00 2001 From: Zach Steindler Date: Mon, 20 Jul 2026 15:05:05 -0400 Subject: [PATCH 4/4] Have communication to firewall be separate from SecretMasker --- src/Runner.Common/CommandLineParser.cs | 5 ++++ src/Runner.Common/HostContext.cs | 11 ++++++++- ...nNotifier.cs => RunnerFirewallNotifier.cs} | 6 ++--- src/Runner.Common/Terminal.cs | 3 +++ src/Runner.Listener/CommandSettings.cs | 3 +++ .../Configuration/ConfigurationManager.cs | 6 +++++ src/Runner.Worker/ActionCommandManager.cs | 9 +++++++ src/Runner.Worker/ActionManager.cs | 6 +++++ src/Runner.Worker/ExecutionContext.cs | 3 +++ src/Runner.Worker/Worker.cs | 12 ++++++++++ .../Logging/ISecretRegistrationNotifier.cs | 23 ------------------ src/Sdk/DTLogging/Logging/SecretMasker.cs | 24 ------------------- src/Test/L0/TestHostContext.cs | 1 + 13 files changed, 61 insertions(+), 51 deletions(-) rename src/Runner.Common/{RunnerSecretRegistrationNotifier.cs => RunnerFirewallNotifier.cs} (95%) delete mode 100644 src/Sdk/DTLogging/Logging/ISecretRegistrationNotifier.cs diff --git a/src/Runner.Common/CommandLineParser.cs b/src/Runner.Common/CommandLineParser.cs index 7880f65f308..2de343fb57d 100644 --- a/src/Runner.Common/CommandLineParser.cs +++ b/src/Runner.Common/CommandLineParser.cs @@ -13,6 +13,7 @@ namespace GitHub.Runner.Common public sealed class CommandLineParser { private ISecretMasker _secretMasker; + private RunnerFirewallNotifier _runnerFirewallNotifier; private Tracing _trace; public List Commands { get; } @@ -24,6 +25,7 @@ public sealed class CommandLineParser public CommandLineParser(IHostContext hostContext, string[] secretArgNames) { _secretMasker = hostContext.SecretMasker; + _runnerFirewallNotifier = hostContext.RunnerFirewallNotifier; _trace = hostContext.GetTrace(nameof(CommandLineParser)); Commands = new List(); @@ -91,6 +93,9 @@ public void Parse(string[] args) if (SecretArgNames.Contains(argScope)) { _secretMasker.AddValue(arg); + _runnerFirewallNotifier.NotifySecretRegistration( + secrets: new List { arg }, + secretRegexes: null); } _trace.Info("Adding option '{0}': '{1}'", argScope, arg); diff --git a/src/Runner.Common/HostContext.cs b/src/Runner.Common/HostContext.cs index 2df100dae3e..52fa245477c 100644 --- a/src/Runner.Common/HostContext.cs +++ b/src/Runner.Common/HostContext.cs @@ -25,6 +25,7 @@ public interface IHostContext : IDisposable CancellationToken RunnerShutdownToken { get; } ShutdownReason RunnerShutdownReason { get; } ISecretMasker SecretMasker { get; } + RunnerFirewallNotifier RunnerFirewallNotifier { get; } List UserAgents { get; } RunnerWebProxy WebProxy { get; } string GetDirectory(WellKnownDirectory directory); @@ -60,7 +61,8 @@ 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(new RunnerSecretRegistrationNotifier()); + private readonly ISecretMasker _secretMasker = new SecretMasker(); + private readonly RunnerFirewallNotifier _runnerFirewallNotifier = new RunnerFirewallNotifier(); private readonly List _userAgents = new() { new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version) }; private CancellationTokenSource _runnerShutdownTokenSource = new(); private object _perfLock = new(); @@ -88,6 +90,7 @@ public sealed class HostContext : EventListener, IObserver, public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token; public ShutdownReason RunnerShutdownReason { get; private set; } public ISecretMasker SecretMasker => _secretMasker; + public RunnerFirewallNotifier RunnerFirewallNotifier => _runnerFirewallNotifier; public List UserAgents => _userAgents; public RunnerWebProxy WebProxy => _webProxy; public bool AllowAuthMigration => _allowAuthMigration.IsSet; @@ -190,6 +193,9 @@ public HostContext(string hostType, string logFile = null) if (!string.IsNullOrEmpty(WebProxy.HttpProxyPassword)) { this.SecretMasker.AddValue(WebProxy.HttpProxyPassword); + this.RunnerFirewallNotifier.NotifySecretRegistration( + secrets: new List { WebProxy.HttpProxyPassword }, + secretRegexes: null); } _trace.Info($"Configuring authenticated proxy {WebProxy.HttpProxyAddress} for all HTTP requests."); @@ -208,6 +214,9 @@ public HostContext(string hostType, string logFile = null) if (!string.IsNullOrEmpty(WebProxy.HttpsProxyPassword)) { this.SecretMasker.AddValue(WebProxy.HttpsProxyPassword); + this.RunnerFirewallNotifier.NotifySecretRegistration( + secrets: new List { WebProxy.HttpsProxyPassword }, + secretRegexes: null); } _trace.Info($"Configuring authenticated proxy {WebProxy.HttpsProxyAddress} for all HTTPS requests."); diff --git a/src/Runner.Common/RunnerSecretRegistrationNotifier.cs b/src/Runner.Common/RunnerFirewallNotifier.cs similarity index 95% rename from src/Runner.Common/RunnerSecretRegistrationNotifier.cs rename to src/Runner.Common/RunnerFirewallNotifier.cs index 5ce48ada5ba..d5e5ea844a3 100644 --- a/src/Runner.Common/RunnerSecretRegistrationNotifier.cs +++ b/src/Runner.Common/RunnerFirewallNotifier.cs @@ -9,14 +9,14 @@ namespace GitHub.Runner.Common { - public sealed class RunnerSecretRegistrationNotifier : ISecretRegistrationNotifier + public sealed class RunnerFirewallNotifier { private const AddressFamily VsockAddressFamily = (AddressFamily)40; internal bool IsLinux { get; } private readonly Socket _vsock; private readonly object _vsockSendLock = new object(); - public RunnerSecretRegistrationNotifier() + public RunnerFirewallNotifier() { IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); _vsock = IsLinux ? TryCreateConnectedVsock() : null; @@ -51,7 +51,7 @@ public void NotifySecretRegistration(List secrets, List secretRe } catch { - // Notification delivery is best-effort and must never break secret masking. + // Notification delivery is best-effort } } diff --git a/src/Runner.Common/Terminal.cs b/src/Runner.Common/Terminal.cs index 75489bc1853..c4ce405e9ce 100644 --- a/src/Runner.Common/Terminal.cs +++ b/src/Runner.Common/Terminal.cs @@ -85,6 +85,9 @@ public string ReadSecret() if (!string.IsNullOrEmpty(val)) { HostContext.SecretMasker.AddValue(val); + HostContext.RunnerFirewallNotifier.NotifySecretRegistration( + secrets: new List { val }, + secretRegexes: null); } Trace.Info($"Read value: '{val}'"); diff --git a/src/Runner.Listener/CommandSettings.cs b/src/Runner.Listener/CommandSettings.cs index 7c29e2b6d2a..51a4fced9bc 100644 --- a/src/Runner.Listener/CommandSettings.cs +++ b/src/Runner.Listener/CommandSettings.cs @@ -127,6 +127,9 @@ public CommandSettings(IHostContext context, string[] args) if (secret) { context.SecretMasker.AddValue(val); + context.RunnerFirewallNotifier.NotifySecretRegistration( + secrets: new List { val }, + secretRegexes: null); } // Store the value. diff --git a/src/Runner.Listener/Configuration/ConfigurationManager.cs b/src/Runner.Listener/Configuration/ConfigurationManager.cs index 360728a1c9b..d5084d29594 100644 --- a/src/Runner.Listener/Configuration/ConfigurationManager.cs +++ b/src/Runner.Listener/Configuration/ConfigurationManager.cs @@ -717,6 +717,9 @@ private async Task GetRunnerTokenAsync(CommandSettings command, string g var jitToken = await GetJITRunnerTokenAsync(githubUrl, githubPAT, tokenType); Trace.Info($"Retrived runner {tokenType} token is good to {jitToken.ExpiresAt}."); HostContext.SecretMasker.AddValue(jitToken.Token); + HostContext.RunnerFirewallNotifier.NotifySecretRegistration( + secrets: new List { jitToken.Token }, + secretRegexes: null); runnerToken = jitToken.Token; } @@ -783,6 +786,9 @@ private async Task GetJITRunnerTokenAsync(string gith { var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"github:{githubToken}")); HostContext.SecretMasker.AddValue(base64EncodingToken); + HostContext.RunnerFirewallNotifier.NotifySecretRegistration( + secrets: new List { base64EncodingToken }, + secretRegexes: null); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("basic", base64EncodingToken); httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents); httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github.v3+json"); diff --git a/src/Runner.Worker/ActionCommandManager.cs b/src/Runner.Worker/ActionCommandManager.cs index 4b9995fc89d..adf9cc9ef10 100644 --- a/src/Runner.Worker/ActionCommandManager.cs +++ b/src/Runner.Worker/ActionCommandManager.cs @@ -116,6 +116,9 @@ public bool TryProcessCommand(IExecutionContext context, string input, Container if (_stopToken.Length > 6) { HostContext.SecretMasker.AddValue(_stopToken); + HostContext.RunnerFirewallNotifier.NotifySecretRegistration( + secrets: new List { _stopToken }, + secretRegexes: null); } context.Output(input); @@ -438,6 +441,9 @@ public void ProcessCommand(IExecutionContext context, string line, ActionCommand } HostContext.SecretMasker.AddValue(command.Data); + HostContext.RunnerFirewallNotifier.NotifySecretRegistration( + secrets: new List { command.Data }, + secretRegexes: null); Trace.Info($"Add new secret mask with length of {command.Data.Length}"); // Also add each individual line. Typically individual lines are processed from STDOUT of child processes. @@ -445,6 +451,9 @@ public void ProcessCommand(IExecutionContext context, string line, ActionCommand foreach (var item in split) { HostContext.SecretMasker.AddValue(item); + HostContext.RunnerFirewallNotifier.NotifySecretRegistration( + secrets: new List { item }, + secretRegexes: null); } } } diff --git a/src/Runner.Worker/ActionManager.cs b/src/Runner.Worker/ActionManager.cs index d9e590cae37..65cd8183479 100644 --- a/src/Runner.Worker/ActionManager.cs +++ b/src/Runner.Worker/ActionManager.cs @@ -1119,6 +1119,9 @@ private async Task BuildActionContainerAsync(IExecutionContext executionContext, { // Add secret HostContext.SecretMasker.AddValue(actionDownloadInfo.Authentication?.Token); + HostContext.RunnerFirewallNotifier.NotifySecretRegistration( + secrets: new List { actionDownloadInfo.Authentication?.Token }, + secretRegexes: null); // Default auth token if (string.IsNullOrEmpty(actionDownloadInfo.Authentication?.Token)) @@ -1637,6 +1640,9 @@ private AuthenticationHeaderValue CreateAuthHeader(IExecutionContext executionCo Trace.Info("Using Basic token for action archive download."); var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{token}")); HostContext.SecretMasker.AddValue(base64EncodingToken); + HostContext.RunnerFirewallNotifier.NotifySecretRegistration( + secrets: new List { base64EncodingToken }, + secretRegexes: null); return new AuthenticationHeaderValue("Basic", base64EncodingToken); } } diff --git a/src/Runner.Worker/ExecutionContext.cs b/src/Runner.Worker/ExecutionContext.cs index 0f9410821c6..17d1f424815 100644 --- a/src/Runner.Worker/ExecutionContext.cs +++ b/src/Runner.Worker/ExecutionContext.cs @@ -1025,6 +1025,9 @@ public void InitializeJob(Pipelines.AgentJobRequestMessage message, Cancellation var githubAccessToken = new StringContextData(Global.Variables.Get("system.github.token")); var base64EncodedToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{githubAccessToken}")); HostContext.SecretMasker.AddValue(base64EncodedToken); + HostContext.RunnerFirewallNotifier.NotifySecretRegistration( + secrets: new List { base64EncodedToken }, + secretRegexes: null); var githubJob = Global.Variables.Get("system.github.job"); var githubContext = new GitHubContext(); githubContext["token"] = githubAccessToken; diff --git a/src/Runner.Worker/Worker.cs b/src/Runner.Worker/Worker.cs index 4784c169314..185b0c3e9e7 100644 --- a/src/Runner.Worker/Worker.cs +++ b/src/Runner.Worker/Worker.cs @@ -154,12 +154,18 @@ private void InitializeSecretMasker(Pipelines.AgentJobRequestMessage message) // Add the entire value, even if it contains CR or LF. During expression tracing, // invidual trace info may contain line breaks. HostContext.SecretMasker.AddValue(value); + HostContext.RunnerFirewallNotifier.NotifySecretRegistration( + secrets: new List { value }, + secretRegexes: null); // Also add each individual line. Typically individual lines are processed from STDOUT of child processes. var split = value.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); foreach (var item in split) { HostContext.SecretMasker.AddValue(item); + HostContext.RunnerFirewallNotifier.NotifySecretRegistration( + secrets: new List { item }, + secretRegexes: null); } } } @@ -174,6 +180,9 @@ private void InitializeSecretMasker(Pipelines.AgentJobRequestMessage message) // We need this because the worker will print out the job message JSON to diag log // and SecretMasker has JsonEscapeEncoder hook up HostContext.SecretMasker.AddValue(maskHint.Value); + HostContext.RunnerFirewallNotifier.NotifySecretRegistration( + secrets: new List { maskHint.Value }, + secretRegexes: new List { maskHint.Value }); } else { @@ -192,6 +201,9 @@ private void InitializeSecretMasker(Pipelines.AgentJobRequestMessage message) if (!string.IsNullOrEmpty(value)) { HostContext.SecretMasker.AddValue(value); + HostContext.RunnerFirewallNotifier.NotifySecretRegistration( + secrets: new List { value }, + secretRegexes: null); } } } diff --git a/src/Sdk/DTLogging/Logging/ISecretRegistrationNotifier.cs b/src/Sdk/DTLogging/Logging/ISecretRegistrationNotifier.cs deleted file mode 100644 index 1a588d17ae7..00000000000 --- a/src/Sdk/DTLogging/Logging/ISecretRegistrationNotifier.cs +++ /dev/null @@ -1,23 +0,0 @@ -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 5f53216f7f6..430b977f521 100644 --- a/src/Sdk/DTLogging/Logging/SecretMasker.cs +++ b/src/Sdk/DTLogging/Logging/SecretMasker.cs @@ -11,17 +11,11 @@ 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) @@ -36,7 +30,6 @@ 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 { @@ -73,8 +66,6 @@ public void AddRegex(String pattern) m_lock.ExitWriteLock(); } } - - NotifySecretRegistration(new List(), new List { pattern }); } /// @@ -142,8 +133,6 @@ public void AddValue(String value) m_lock.ExitWriteLock(); } } - - NotifySecretRegistration(new List { value }, new List()); } /// @@ -304,19 +293,6 @@ 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. - } - } } } diff --git a/src/Test/L0/TestHostContext.cs b/src/Test/L0/TestHostContext.cs index c1cf692204f..7326b2e592d 100644 --- a/src/Test/L0/TestHostContext.cs +++ b/src/Test/L0/TestHostContext.cs @@ -34,6 +34,7 @@ public sealed class TestHostContext : IHostContext, IDisposable public CancellationToken RunnerShutdownToken => _runnerShutdownTokenSource.Token; public ShutdownReason RunnerShutdownReason { get; private set; } public ISecretMasker SecretMasker => _secretMasker; + public RunnerFirewallNotifier RunnerFirewallNotifier { get; } = new RunnerFirewallNotifier(); public TestHostContext(object testClass, [CallerMemberName] string testName = "") {