Fix log masking: preserve atomic writes and keep token prefixes visible
What this does
Fixes two masking issues in streamer/sanitizer + phrasestream.
1. Masker no longer fragments a caller's Write
The phrase matcher emits its output in pieces — plaintext flushed ahead of a candidate phrase prefix, single bytes replayed after a failed match, and mask callbacks — and each piece was written straight through. So one Write from a producer became several downstream writes.
Downstream, the \r-flushing timestamper starts a new trace entry for any write containing a carriage return. The transfer meter emits each progress update as one atomic, \r-led write ("\rUploading cache <n>/<total> (<rate>)…"). When the job has a masked secret, the matcher split that frame at an interior byte, so the timestamper emitted it across two entries with two timestamps — visibly breaking the word:
… 01O+\rUploading cach
… 01O+e 0 B/7.30 GB (0 B/s) \rUploading cachThe masker now buffers a single top-level Write's output and flushes it once, preserving the caller's write boundary. The bytes emitted and the masking decisions are unchanged — only the batching.
2. Token masking keeps the prefix
Token-prefix masking replaced the whole match including the prefix (glpat-abc… → [MASKED]). It now masks only the token body and keeps the prefix (glpat-abc… → glpat-[MASKED]), matching GitLab Runner's masker. This reuses the callback already used for URL-parameter masking, which keeps the matched key in place.
Why
- The fragmented progress meter only reproduces when the job has masked variables, which is why it slipped through.
- Keeping the token prefix matches GitLab Runner's long-standing behaviour — logs stay readable while the secret body is still hidden.
Testing
- New
phrasestreamtest: a single top-levelWritecrossing a phrase prefix yields a single downstream write with unchanged bytes, and completed phrases are still masked. - Updated sanitizer tests assert
glpat-[MASKED](prefix kept, body masked). - Full
streamer/...andpkg/api/...suites pass;go vetandgo build ./...clean.
Closes Masker fragments a caller's Write, breaking \r-... (#471 - closed)
Closes Token-prefix masking hides the prefix instead o... (#472 - closed)