Skip to content

fix(logging): pass log content as a template argument instead of the format string - #152

Closed
senrecep wants to merge 1 commit into
mainfrom
fix/logging-log-forging
Closed

senrecep wants to merge 1 commit into
mainfrom
fix/logging-log-forging

Conversation

@senrecep

Copy link
Copy Markdown
Owner

Closes the two CodeQL cs/log-forging alerts (#7, #8) from #146 item A.

  • LoggerFactoryLogWriter: when the creator returns no values, content is logged as the argument of constant template {LogMessage}.
  • Rendered message text unchanged; structured state now carries LogMessage.
  • Test added (guard: braces in body kept verbatim, template constant). It was green before the change: Log(level, msg) with no args does not format, so this was a scanner/hygiene fix, not a runtime bug.

🤖 Generated with Claude Code

…format string

Request and response content was concatenated into the message template
passed to ILogger.Log, flagged by CodeQL cs/log-forging. The content now
travels as the {LogMessage} argument of a constant template; rendered
output is unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!


if (values is null)
{
_logger.Log(level, "{LogMessage}", logString);

object?[] parameters = [.. values];
#pragma warning disable CA2254
_logger.Log(level, logString, parameters);

object?[] parameters = [.. values];
#pragma warning disable CA2254
_logger.Log(level, logString, parameters);
@senrecep

Copy link
Copy Markdown
Owner Author

CodeQL still flags the template argument (alerts #13-#15): cs/log-forging treats any ILogger argument as a sink unless CR/LF are stripped, and stripping would corrupt the bodies this package exists to log. The change brings no benefit, so closing it.

@senrecep senrecep closed this Oct 10, 2026
@kathelon

Copy link
Copy Markdown
Contributor

Hi @senrecep, a follow-up question on this one before I open anything.

I agree that stripping CR/LF would corrupt the logged bodies. I tried a variant that escapes them instead of stripping: \r, \n, \v, \f, U+0085, U+2028 and U+2029 become the literal text \r, \n, … in every logged value. Nothing is dropped. The cost is that a multi-line body (e.g. pretty-printed JSON) is logged on one line. Backslashes are not escaped, so an escaped line break reads the same as those characters sent literally.

The rest of the change:

  • LoggerFactoryLogWriter logs a small custom IReadOnlyList<KeyValuePair<string, object?>> state with a constant {OriginalFormat}: the per-field template, or {RequestResponseLog:l} when UseSeparateContext = false. This removes the #pragma warning disable CA2254 at its root.
  • The rendered text is unchanged for input without line breaks (pinned by tests).
  • IsEnabled is checked before the message is built.

Why I think it might be worth it beyond the scanner:

  • With the current output format (Field: value per line), a body like ok\nPath: /admin produces a line that looks like a real field in plain-text sinks.
  • Since the sanitizer is a string.Replace chain on CR/LF, I expect it to clear cs/log-forging. I couldn't run CodeQL locally, so CI would have to confirm that.

Branch with tests (test-first; 82/82 logging tests green): https://github.com/kathelon/CSharpEssentials/tree/fix/logging-log-forging

Would you be open to a PR with this approach, possibly behind an option if you'd rather not change the default output? If the one-line-body trade-off is a no-go, no problem; I'll drop it.

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.

3 participants