Every intercepted Console.Write* that completes a line executes ObfuscateCompletePatterns — StringBuilder.ToString() allocation plus FindFirstPattern, which loops all secret patterns doing input.IndexOf(pattern, OrdinalIgnoreCase) with no SearchValues gate (O(patterns × length) even when no secret is present) — then GetPotentialPatternPrefixLength (an O(prefixLen² × patterns) StartsWith loop) is computed a second time in FlushSafeOutput, and WriteCompletedLine runs the full SecretObfuscator.Obfuscate scan a third time over text already cleaned. All of this happens while holding the single _lineBufferLock, serializing console output from all concurrently executing modules.
Note this is distinct from the fixed #3255 (per-character locking / O(n²) buffer scans): these are residual hot-path costs in the current implementation.
Fix: gate with the obfuscator's existing SearchValues.ContainsAny before any per-pattern loop, compute the retained-prefix length once per flush, skip the redundant final Obfuscate for text already processed, and shrink the locked region to buffer mutation only.
Evidence: src/ModularPipelines/Console/CoordinatedTextWriter.cs:70-128,222-268,232,270-282,317-336,368-386,400-412
Raised by the v4 scoping audit round 3 — umbrella #3239.
Every intercepted
Console.Write*that completes a line executesObfuscateCompletePatterns—StringBuilder.ToString()allocation plusFindFirstPattern, which loops all secret patterns doinginput.IndexOf(pattern, OrdinalIgnoreCase)with noSearchValuesgate (O(patterns × length) even when no secret is present) — thenGetPotentialPatternPrefixLength(an O(prefixLen² × patterns)StartsWithloop) is computed a second time inFlushSafeOutput, andWriteCompletedLineruns the fullSecretObfuscator.Obfuscatescan a third time over text already cleaned. All of this happens while holding the single_lineBufferLock, serializing console output from all concurrently executing modules.Note this is distinct from the fixed #3255 (per-character locking / O(n²) buffer scans): these are residual hot-path costs in the current implementation.
Fix: gate with the obfuscator's existing
SearchValues.ContainsAnybefore any per-pattern loop, compute the retained-prefix length once per flush, skip the redundant finalObfuscatefor text already processed, and shrink the locked region to buffer mutation only.Evidence:
src/ModularPipelines/Console/CoordinatedTextWriter.cs:70-128,222-268,232,270-282,317-336,368-386,400-412Raised by the v4 scoping audit round 3 — umbrella #3239.