fix(api): strip email PII from remaining email/marketing log sites (#243)#331
Conversation
) Removes recipient email addresses and raw provider response bodies from the log templates that merged PR #325 left untouched, keeping only non-PII diagnostics (subject, HTTP status, a user/correlation id, error): - VerifyCodeCommand: LogWelcomeEmailFailed logs {UserId} instead of {Email}. - ResendEmailService: LogEmailSent/LogEmailFailed/LogEmailSendException/ LogSkippingTestEmail/LogMarketingRetry drop the recipient {To}; LogEmailFailed also drops the raw response {Body} (which can echo submitted PII) and keeps the HTTP status. The two now-orphaned response-body reads are removed. - SendMarketingBroadcastCommand: LogPreviewSent logs {Subject} instead of the {TestEmail}; LogBroadcastQueued drops the redundant {QueuedAtUtc} timestamp (the logging framework already stamps each entry, and it tripped the tz rule). - AuthController: the Information-level LogVerificationCodeSent drops {Email} (RequestId already correlates) and LogUserLoggedInViaCode logs {UserId}. Adds capturing-logger tests asserting the email address and response body never reach the rendered log line while status/subject/user id survive. Refs thomasluizon/orbit-ui-mobile#243 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
PR Review — #331: fix(api): strip email PII from remaining email/marketing log sites
Recommendation: APPROVE
Summary
Narrow, logging-only PII scrub: removes recipient email addresses and raw HTTP response bodies from nine [LoggerMessage] sites across AuthController.cs, VerifyCodeCommand.cs, SendMarketingBroadcastCommand.cs, and ResendEmailService.cs, replacing them with non-PII diagnostics (UserId, subject, HTTP status). No endpoint, DTO, or contract surface is touched.
| Severity | Count |
|---|---|
| Critical | 0 |
| High | 0 |
| Medium | 0 |
| Low / Info | 1 |
Findings
Low/Info (non-blocking) — Three test files (AuthControllerTests.cs, SendMarketingBroadcastCommandHandlerTests.cs, ResendEmailServicePiiScrubbingTests.cs) each add their own private CollectingLogger<T> : ILogger<T> test double (~15 identical lines). This mirrors a pre-existing pattern already in the suite (AiRetryLoggingPolicyTests.cs has its own copy too), so it's not a regression — just a note that a shared test helper could be hoisted if a fourth copy appears.
Verified
- No stale callers of the old
[LoggerMessage]signatures remain anywhere insrc/ortests/. - The two now-orphaned
response.Content.ReadAsStringAsync(...)reads were correctly deleted;using (response)disposal scopes are preserved. LoginResponserecord shape,AuthControllerroutes/request records, andSendMarketingBroadcastCommand's public shape are unchanged — no contract surface moved.- Test-added constructor calls match current source signatures.
- Log levels are unchanged everywhere, matching the PR's stated scope.
LogBroadcastQueuedalso dropped itsDateTime.UtcNowparam as a bonuscsharp-tz-hook fix, called out in the PR body.
Independent security review (security-reviewer subagent)
Result: PASS, no issues introduced.
- Scrubbing is complete at every modified log site; only
UserId(Guid, not PII), subject, status, attempt/backoff remain. [Authorize]/[AllowAnonymous]/[DistributedRateLimit]attributes on auth endpoints are untouched.- Response-body disposal (
using (response)) is unaffected by the body-read removal. - Flagged one pre-existing, out-of-scope residual for a future PR:
ResendEmailService.cs:43builds asignInUrlembedding the raw verification code/email into a URL — not a log site, not a regression from this diff.
Deferred / not verifiable in CI
- Contract-aligner /
orbit-ui-mobilebackward-compat: sibling repo not checked out this run; structurally N/A here since no DTO/endpoint field changed. - Phase 6 (dotnet build/test): skipped — Build/Unit Tests/SonarCloud run as separate required CI checks on this PR.
Lifts new-code coverage over the SonarCloud gate by exercising the IsEnabled-guarded log lines that existing NullLogger tests skip. Each new case asserts the recipient email never reaches the rendered marketing log line (success, retry/rate-limit, send exception, and both test-account skip paths), completing the PII contract across every ResendEmailService log site. Refs thomasluizon/orbit-ui-mobile#243 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
There was a problem hiding this comment.
Code Review: PR #331
Scope: fix(api): strip email PII from remaining email/marketing log sites (+ follow-up test commit)
Recommendation: APPROVE
Summary
This PR finishes the PII-scrub sweep left out of scope by #325: it removes recipient email addresses and raw provider response bodies from the remaining email/marketing log sites in AuthController, VerifyCodeCommand, SendMarketingBroadcastCommand, and ResendEmailService, replacing them with non-PII diagnostics (UserId, Subject, HttpStatusCode). A follow-up commit (418b7ba) adds unit-test coverage for the marketing success/retry/exception and test-account skip log paths — test-only, no production code changed. It's a pure internal-logging change with no endpoint/DTO/contract surface touched.
Findings
Critical
None.
High
None.
Medium
[MEDIUM] Duplicate CollectingLogger<T> capturing-logger test double defined identically in multiple files across the test suite
· location: tests/Orbit.Infrastructure.Tests/Controllers/AuthControllerTests.cs, tests/Orbit.Infrastructure.Tests/Services/ResendEmailServicePiiScrubbingTests.cs (plus a non-generic variant already in AiRetryLoggingPolicyTests.cs and another copy in Orbit.Application.Tests)
· issue: The same small ILogger<T>-capturing test double is redeclared in each file instead of living in one shared test-support location.
· risk: Low — nothing breaks today, but a future fix to the helper (e.g. scope capture, thread-safety) would need to be applied in multiple places.
· fix: Extract a single CollectingLogger<T> into a shared test helper in tests/Orbit.Infrastructure.Tests/ and reference it from both files. Not a blocker for this PR.
Low / Info
None surfaced (signal gate).
Validation
| Check | Result |
|---|---|
| Build / Unit Tests / SonarCloud | Deferred to this PR's separate required CI checks |
What's good
- The scrub is thorough: every log site the PR claims to fix has verified changes —
to/Emailparams are gone from[LoggerMessage]signatures, replaced withUserId(auth) orSubject/Status(email), and orphanedresponse.Content.ReadAsStringAsync()reads were removed alongside the{Body}params that consumed them. - New capturing-logger tests assert the negative (
entry.Should().NotContain(email)/OnlyContain(!Contains(email))) rather than only the positive shape — the right test for a PII-scrub change. - The follow-up commit closes the remaining coverage gaps (marketing retry/exception/skip paths, transactional skip path) without touching production code.
Recommendation
Safe to merge as-is. The Medium (test-helper duplication) is a nice-to-have cleanup, not a blocker.



Removes recipient email addresses and raw provider response bodies from the log sites that merged PR #325 deliberately left out of its scope, so no PII lands in prod logs or Sentry. Each site keeps its non-PII diagnostics (subject, HTTP status, a user/correlation id, error).
Log sites scrubbed
VerifyCodeCommand.LogWelcomeEmailFailedfor user {Email}for user {UserId}ResendEmailService.LogEmailSentto {To} subject={Subject}subject={Subject}ResendEmailService.LogEmailFailedto {To} status={Status} body={Body}subject={Subject} status={Status}ResendEmailService.LogEmailSendExceptionto {To}ResendEmailService.LogSkippingTestEmail{To} subject={Subject}subject={Subject}ResendEmailService.LogMarketingRetryto {To} ......(attempt/backoff only)SendMarketingBroadcastCommand.LogPreviewSentto {TestEmail}subject={Subject}AuthController.LogVerificationCodeSentto {Email}. RequestId=RequestId=AuthController.LogUserLoggedInViaCodecode {Email}. RequestId=UserId={UserId} RequestId=Cleanup pulled in (same-PR, maximum-implementation)
response.Content.ReadAsStringAsyncreads that only fed{Body}are removed.LogBroadcastQueueddrops its redundant{QueuedAtUtc}param — the logging framework already timestamps every entry, and theDateTime.UtcNowtripped thecsharp-tzrule.Scope
Internal logging only — no endpoint, DTO, or contract change, so no
packages/shared/consumer edits. Levels unchanged (task is a PII scrub, not a level change). GoogleAuth's welcome-email + login-via-Google logs were already PII-free from #325.Tests
ResendEmailServicePiiScrubbingTests(capturing logger): success/failure/exception/marketing paths assert the recipient email and raw response body never reach the rendered log line, while status/subject survive.AuthControllerTestscapturing-logger cases: send-code and login-via-code logs omit the email; the login log carries theUserId.SendMarketingBroadcastCommandHandlerTestscase: the preview log omits the test email and carries the subject.Orbit.Infrastructure.Tests(1540) +Orbit.Application.Tests(2629) green locally.Refs thomasluizon/orbit-ui-mobile#243