Since #94 landed, Process.StartAsync calls BeginErrorReadLine(). That is a real fix: before it, RedirectStandardError was set but the stream was never drained, so stderr was silently discarded and a child writing past the pipe buffer would hang forever (verified: a step emitting 500 KB to stderr never returns on 4ce7911, completes on master).
The side effect is that the child's stderr is now printed for the first time, and the RunSensitive* members only mask the command line, not the child's output.
Secrets leak into the log
let token = "SUPERSECRET123"
pipeline "t" {
stage "s" {
run (fun ctx -> ctx.RunSensitiveCommand $"sh -c \"echo Authorization: Bearer {token} >&2\"")
}
runIfOnlySpecified false
}
Before #94:
sh -c "echo Authorization: Bearer * >&2"
On master:
sh -c "echo Authorization: Bearer * >&2"
Authorization: Bearer SUPERSECRET123
The masked line still prints, so the log looks like masking worked. Anything that echoes credentials on stderr is now exposed: curl -v request headers, ssh -v, git with GIT_TRACE, tools that warn about a token on argv.
This affects master only. The latest version on NuGet is 1.1.18, which predates #94, so no released version is impacted.
Suggested fix: the interpolated values should be masked in the child's output, not only in the command string.
stderr is reprinted on stdout
ProcessExtensions.fs handles both streams with the same callback, which ends in Console.WriteLine. So the child's stderr is written to the parent's stdout.
Running a build as dotnet fsi build.fsx >out.txt 2>err.txt puts the child's stderr lines into out.txt and leaves err.txt empty. Any CI wrapper or problem matcher that classifies failure by "stderr is non-empty" now sees a clean run. Console.Error.WriteLine for the stderr pump would preserve the separation.
Edited to drop a third section that asked for deterministic ordering between the two streams. That cannot be done without merging them, which would undo the separation asked for above, so it is out of scope. Worth noting that splitting the streams does give up the serialisation both pumps got for free from sharing Console.Out, so a fix should take a lock to keep each line atomic.
Since #94 landed,
Process.StartAsynccallsBeginErrorReadLine(). That is a real fix: before it,RedirectStandardErrorwas set but the stream was never drained, so stderr was silently discarded and a child writing past the pipe buffer would hang forever (verified: a step emitting 500 KB to stderr never returns on 4ce7911, completes on master).The side effect is that the child's stderr is now printed for the first time, and the
RunSensitive*members only mask the command line, not the child's output.Secrets leak into the log
Before #94:
On master:
The masked line still prints, so the log looks like masking worked. Anything that echoes credentials on stderr is now exposed:
curl -vrequest headers,ssh -v,gitwithGIT_TRACE, tools that warn about a token on argv.This affects master only. The latest version on NuGet is 1.1.18, which predates #94, so no released version is impacted.
Suggested fix: the interpolated values should be masked in the child's output, not only in the command string.
stderr is reprinted on stdout
ProcessExtensions.fshandles both streams with the same callback, which ends inConsole.WriteLine. So the child's stderr is written to the parent's stdout.Running a build as
dotnet fsi build.fsx >out.txt 2>err.txtputs the child's stderr lines intoout.txtand leaveserr.txtempty. Any CI wrapper or problem matcher that classifies failure by "stderr is non-empty" now sees a clean run.Console.Error.WriteLinefor the stderr pump would preserve the separation.Edited to drop a third section that asked for deterministic ordering between the two streams. That cannot be done without merging them, which would undo the separation asked for above, so it is out of scope. Worth noting that splitting the streams does give up the serialisation both pumps got for free from sharing
Console.Out, so a fix should take a lock to keep each line atomic.