Category: secret hygiene — Breaking: no
Rotate/expire exposure first if applicable — this defect has already printed a live job token in public run logs.
The startup environment-variables table (Engine/Executors/PipelineInitializer.cs:56-77) renders values in a Spectre table that wraps at the column width (~37 chars). GitHub's runner-side ::add-mask:: masking only matches a whole secret appearing on a single physical line, so any env secret longer than the column width is printed in full across multiple table continuation rows, unmasked. Observed on recent main runs (e.g. 30526435350): the workflow-provided GITHUB_TOKEN (job-scoped, 1h expiry) appears complete across 11 wrapped rows, while short secrets on the same table are correctly masked. The framework's own obfuscator can't help when the value was never registered as a [SecretValue] (here the workflow deliberately unsets the registered copy on main pushes).
Fix: never hard-wrap env values — emit one physical line per value (truncate long values for display instead of wrapping) so runner masking can match; additionally apply name-heuristic masking (TOKEN|SECRET|PASSWORD|KEY|PWD|CREDENTIAL) in the env table regardless of secret registration. Any consumer with a >37-char env secret leaks it permanently today.
Part of #3239 (round 2 addendum). Distinct from #3475 (PipelineLevelLogger path).
Category: secret hygiene — Breaking: no
Rotate/expire exposure first if applicable — this defect has already printed a live job token in public run logs.
The startup environment-variables table (
Engine/Executors/PipelineInitializer.cs:56-77) renders values in a Spectre table that wraps at the column width (~37 chars). GitHub's runner-side::add-mask::masking only matches a whole secret appearing on a single physical line, so any env secret longer than the column width is printed in full across multiple table continuation rows, unmasked. Observed on recent main runs (e.g. 30526435350): the workflow-providedGITHUB_TOKEN(job-scoped, 1h expiry) appears complete across 11 wrapped rows, while short secrets on the same table are correctly masked. The framework's own obfuscator can't help when the value was never registered as a[SecretValue](here the workflow deliberately unsets the registered copy on main pushes).Fix: never hard-wrap env values — emit one physical line per value (truncate long values for display instead of wrapping) so runner masking can match; additionally apply name-heuristic masking (
TOKEN|SECRET|PASSWORD|KEY|PWD|CREDENTIAL) in the env table regardless of secret registration. Any consumer with a >37-char env secret leaks it permanently today.Part of #3239 (round 2 addendum). Distinct from #3475 (PipelineLevelLogger path).