feat: add secure WP7-02 external executor
Dada P0-A isolated Windows CI / validate-and-package (push) Failing after 48s

This commit is contained in:
suyx
2026-08-04 15:46:38 +08:00
parent 597f4647ef
commit c2f89453a2
6 changed files with 520 additions and 10 deletions
+50
View File
@@ -37,8 +37,58 @@ internal interface ICredentialStore
internal sealed class MissingCredentialException(string target)
: InvalidOperationException($"Required credential is not configured: {target}");
internal sealed record CredentialProcessResult(int ExitCode, string StandardOutput, string StandardError, bool SensitiveOutputDetected);
internal static class CredentialProcessLauncher
{
internal static async Task<CredentialProcessResult> RunToCompletionAsync(
ProcessStartInfo startInfo,
ChildRole role,
ICredentialStore store,
CancellationToken cancellationToken = default)
{
var credentials = new Dictionary<string, string>(StringComparer.Ordinal);
foreach (var target in CredentialCatalog.RequiredFor(role))
{
credentials[target] = store.Read(target) ?? throw new MissingCredentialException(target);
}
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
startInfo.RedirectStandardInput = true;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
using var process = Process.Start(startInfo) ?? throw new InvalidOperationException("Unable to start credential child process.");
var outputTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
var errorTask = process.StandardError.ReadToEndAsync(cancellationToken);
var payload = JsonSerializer.SerializeToUtf8Bytes(credentials);
try
{
await process.StandardInput.BaseStream.WriteAsync(payload, cancellationToken);
await process.StandardInput.BaseStream.FlushAsync(cancellationToken);
process.StandardInput.Close();
await process.WaitForExitAsync(cancellationToken);
var output = await outputTask;
var error = await errorTask;
var sensitive = credentials.Values.Where(value => value.Length > 0).Any(value =>
output.Contains(value, StringComparison.Ordinal) || error.Contains(value, StringComparison.Ordinal));
return sensitive
? new CredentialProcessResult(1, string.Empty, string.Empty, true)
: new CredentialProcessResult(process.ExitCode, output, error, false);
}
catch
{
if (!process.HasExited) process.Kill(entireProcessTree: true);
throw;
}
finally
{
Array.Clear(payload);
foreach (var target in credentials.Keys.ToArray()) credentials[target] = string.Empty;
credentials.Clear();
}
}
internal static async Task<Process> StartAsync(
ProcessStartInfo startInfo,
ChildRole role,