mirror of
https://github.com/actions/runner.git
synced 2025-12-10 20:36:49 +00:00
PowerShell secret masking (#1258)
* Trim pwsh special chars when masking secrets * Add pwsh valueEncoder * Explain regex * Update ValueEncoders.cs * Add tests for pwsh color codes in secrets * Formatting * Group tests into theories * Split secret on PS chars and mask for them * Clean up comments * Remove unused unittest * Rename escape methods
This commit is contained in:
@@ -90,6 +90,8 @@ namespace GitHub.Runner.Common
|
|||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.UriDataEscape);
|
this.SecretMasker.AddValueEncoder(ValueEncoders.UriDataEscape);
|
||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.XmlDataEscape);
|
this.SecretMasker.AddValueEncoder(ValueEncoders.XmlDataEscape);
|
||||||
this.SecretMasker.AddValueEncoder(ValueEncoders.TrimDoubleQuotes);
|
this.SecretMasker.AddValueEncoder(ValueEncoders.TrimDoubleQuotes);
|
||||||
|
this.SecretMasker.AddValueEncoder(ValueEncoders.PowerShellPreAmpersandEscape);
|
||||||
|
this.SecretMasker.AddValueEncoder(ValueEncoders.PowerShellPostAmpersandEscape);
|
||||||
|
|
||||||
// Create the trace manager.
|
// Create the trace manager.
|
||||||
if (string.IsNullOrEmpty(logFile))
|
if (string.IsNullOrEmpty(logFile))
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.Logging
|
namespace GitHub.DistributedTask.Logging
|
||||||
@@ -80,6 +81,65 @@ namespace GitHub.DistributedTask.Logging
|
|||||||
return trimmed;
|
return trimmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String PowerShellPreAmpersandEscape(String value)
|
||||||
|
{
|
||||||
|
// if the secret is passed to PS as a command and it causes an error, sections of it can be surrounded by color codes
|
||||||
|
// or printed individually.
|
||||||
|
|
||||||
|
// The secret secretpart1&secretpart2&secretpart3 would be split into 2 sections:
|
||||||
|
// 'secretpart1&secretpart2&' and 'secretpart3'. This method masks for the first section.
|
||||||
|
|
||||||
|
// The secret secretpart1&+secretpart2&secretpart3 would be split into 2 sections:
|
||||||
|
// 'secretpart1&+' and (no 's') 'ecretpart2&secretpart3'. This method masks for the first section.
|
||||||
|
|
||||||
|
var trimmed = string.Empty;
|
||||||
|
if (!string.IsNullOrEmpty(value) && value.Contains("&"))
|
||||||
|
{
|
||||||
|
var secretSection = string.Empty;
|
||||||
|
if (value.Contains("&+"))
|
||||||
|
{
|
||||||
|
secretSection = value.Substring(0, value.IndexOf("&+") + "&+".Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
secretSection = value.Substring(0, value.LastIndexOf("&") + "&".Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't mask short secrets
|
||||||
|
if (secretSection.Length >= 6)
|
||||||
|
{
|
||||||
|
trimmed = secretSection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String PowerShellPostAmpersandEscape(String value)
|
||||||
|
{
|
||||||
|
var trimmed = string.Empty;
|
||||||
|
if (!string.IsNullOrEmpty(value) && value.Contains("&"))
|
||||||
|
{
|
||||||
|
var secretSection = string.Empty;
|
||||||
|
if (value.Contains("&+"))
|
||||||
|
{
|
||||||
|
// +1 to skip the letter that got colored
|
||||||
|
secretSection = value.Substring(value.IndexOf("&+") + "&+".Length + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
secretSection = value.Substring(value.LastIndexOf("&") + "&".Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (secretSection.Length >= 6)
|
||||||
|
{
|
||||||
|
trimmed = secretSection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
private static string Base64StringEscapeShift(String value, int shift)
|
private static string Base64StringEscapeShift(String value, int shift)
|
||||||
{
|
{
|
||||||
var bytes = Encoding.UTF8.GetBytes(value);
|
var bytes = Encoding.UTF8.GetBytes(value);
|
||||||
|
|||||||
@@ -112,6 +112,36 @@ namespace GitHub.Runner.Common.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("secret&secret&secret", "secret&secret&\x0033[96msecret\x0033[0m", "***\x0033[96m***\x0033[0m")]
|
||||||
|
[InlineData("secret&secret+secret", "secret&\x0033[96msecret+secret\x0033[0m", "***\x0033[96m***\x0033[0m")]
|
||||||
|
[InlineData("secret+secret&secret", "secret+secret&\x0033[96msecret\x0033[0m", "***\x0033[96m***\x0033[0m")]
|
||||||
|
[InlineData("secret&secret&+secretsecret", "secret&secret&+\x0033[96ms\x0033[0mecretsecret", "***\x0033[96ms\x0033[0m***")]
|
||||||
|
[InlineData("secret&+secret&secret", "secret&+\x0033[96ms\x0033[0mecret&secret", "***\x0033[96ms\x0033[0m***")]
|
||||||
|
[InlineData("secret&+secret&+secret", "secret&+\x0033[96ms\x0033[0mecret&+secret", "***\x0033[96ms\x0033[0m***")]
|
||||||
|
[InlineData("secret&+secret&secret&+secret", "secret&+\x0033[96ms\x0033[0mecret&secret&+secret", "***\x0033[96ms\x0033[0m***")]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Common")]
|
||||||
|
public void SecretSectionMasking(string secret, string rawOutput, string maskedOutput)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange.
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
_hc.SecretMasker.AddValue(secret);
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
Assert.Equal(maskedOutput, _hc.SecretMasker.MaskSecrets(rawOutput));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Cleanup.
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Common")]
|
[Trait("Category", "Common")]
|
||||||
|
|||||||
Reference in New Issue
Block a user