Skip to content

Sanitise the log in a shape CodeQL can see - #38

Merged
ivanvyd merged 1 commit into
mainfrom
fix/log-forging-sanitizer
Jul 29, 2026
Merged

Sanitise the log in a shape CodeQL can see#38
ivanvyd merged 1 commit into
mainfrom
fix/log-forging-sanitizer

Conversation

@ivanvyd

@ivanvyd ivanvyd commented Jul 29, 2026

Copy link
Copy Markdown
Owner

The log-forging fix in #32 worked, and CodeQL kept reporting it. Twelve cs/log-forging alerts stand against main right now, on code that is in fact fixed.

I got the verification wrong. I read refs/pull/32/merge reporting 0 alerts as proof the fix was recognised. That ref simply had no analysis — the 0 meant no data, not no findings. The scan of the merge commit itself reports 12.

Why the old shape wasn't recognised

if (!name.Any(char.IsControl)) { return name; }
return new string([.. name.Select(c => char.IsControl(c) ? '\uFFFD' : c)]);

CodeQL's C# taint tracking doesn't model a filter-inside-a-Select as sanitising the flow, so the tainted route value still reaches the log as far as the query is concerned.

The new shape

internal static string ForLog(string name) => name
    .Replace('\r', Redacted)
    .Replace('\n', Redacted)
    .Replace('\u001b', Redacted);

Chained string.Replace is a shape the query recognises. ESC joins CR and LF because a terminal tailing a log acts on escape sequences and a route can carry one.

This is a narrowing, deliberately. char.IsControl covered every control character; this covers the three that actually do something. The rest are ugly in a log rather than dangerous.

An alert nobody can close is an alert everybody learns to scroll past — that's the real cost of leaving twelve standing on correct code.

Verification

448 tests green on both net8.0 and net10.0. Assert.Same still holds for an ordinary name: string.Replace returns the same reference when nothing matches, so the common path still allocates nothing.

New test for the ESC case; the existing CR/LF test is unchanged and still passes.

The real check is CodeQL itself — whether the alert count on main goes from 12 to 0 after this merges. I'll confirm that rather than assert it.

The log-forging fix worked and CodeQL kept reporting it. Twelve
cs/log-forging alerts stood against main after the fix merged, because the
sanitiser was a filter over char.IsControl inside a Select, and CodeQL's C#
taint tracking does not model that as sanitising the flow. I read
"refs/pull/32/merge reports 0 alerts" as proof it was clear; that ref simply
had no analysis, so the 0 meant no data. The scan on the merge commit itself
reports 12.

Chained string.Replace does the same job in a shape the query recognises. CR
and LF as before, and ESC with them -- a terminal tailing a log acts on escape
sequences, and a route can carry one. The narrowing is deliberate and it is a
narrowing: char.IsControl covered every control character, this covers the
three that do something. The rest are ugly in a log rather than dangerous.

An alert nobody can close is an alert everybody learns to scroll past, which
is the real cost of leaving twelve of them standing on correct code.
@ivanvyd
ivanvyd merged commit bc40b78 into main Jul 29, 2026
8 checks passed
@ivanvyd
ivanvyd deleted the fix/log-forging-sanitizer branch July 30, 2026 02:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant