Skip to content

fix(security): remove email from adapter logs for GDPR data minimization#3

Merged
Pomdapis merged 3 commits intomainfrom
pom-178-gdpr-remove-email-from-logs
Apr 25, 2026
Merged

fix(security): remove email from adapter logs for GDPR data minimization#3
Pomdapis merged 3 commits intomainfrom
pom-178-gdpr-remove-email-from-logs

Conversation

@Pomdapis
Copy link
Copy Markdown
Contributor

Context

Closes POM-178. Resolves 14 CodeQL alerts (#4-#16, rule cs/exposure-of-sensitive-information).

Decision: Option 3 (Remove, not mask)

Per RGPD Art 4(5), masked emails (j***@acme.com) are still PII (pseudonymisation, not anonymisation). In B2B context, masked-email + tenant + timestamp permits re-identification. Option 3 = true data minimization (Art 5(1)(c)).

Changes

  • Removed {Email} from log sites in Listmonk, LemonSqueezy, and Stripe adapters.
  • Replaced with non-PII identifiers: {SubscriberId}, {CustomerId}, or Activity.Current?.Id (W3C TraceContext).
  • Renamed Zitadel's existing {EmailHashPrefix} placeholder to {HashPrefix} so the verification grep is clean. The value (HashPrefix(email)) was already PII-safe; only the placeholder name changed.
  • Email still passed to external APIs (Listmonk/Stripe/LemonSqueezy) -- they need it as legitimate input. Only logs changed.
  • Added Compendium.Adapters.Shared.Logging.PiiMasking.MaskEmail helper as anti-regression safety net (unused by this PR).

Verification

  • dotnet build Compendium.sln -c Release
  • dotnet test Compendium.sln -c Release --no-build --filter "FullyQualifiedName!~IntegrationTests&FullyQualifiedName!~LoadTests"
  • grep -rE 'logger\.(Log|log)[A-Za-z]+\(.*\{Email' src/Adapters returns 0 hits.
  • Post-merge: 14 CodeQL alerts should flip to fixed.

Test plan

Refs POM-178. Closes CodeQL alerts #4-#16 (cs/exposure-of-sensitive-information).

Per RGPD Art 5(1)(c) data minimization: replaced {Email} log placeholders
with non-PII identifiers (subscriber_id, customer_id, Activity.Current?.Id).
Email is still passed to Listmonk/Stripe/LemonSqueezy APIs as legitimate
input -- only logs changed.

Also renamed Zitadel's existing {EmailHashPrefix} placeholder to {HashPrefix}
so the verification grep ('logger.Log...{Email') returns 0 hits.

Added Compendium.Adapters.Shared.Logging.PiiMasking.MaskEmail helper as
anti-regression safety net (unused by this PR; reserved for future debug
contexts where subscriber_id/customer_id are unavailable).
Copilot AI review requested due to automatic review settings April 25, 2026 05:07
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces PII exposure in adapter logging by removing raw email addresses from log templates (per GDPR data-minimization) and replacing them with non-PII identifiers such as subscriber/customer IDs and trace/activity IDs, plus a small shared helper for optional email masking.

Changes:

  • Removed {Email} from log statements across Listmonk, Stripe, and LemonSqueezy adapters; replaced with {SubscriberId}/{ActivityId}.
  • Adjusted Zitadel log template placeholder naming for the email hash prefix.
  • Added Compendium.Adapters.Shared project containing a PiiMasking.MaskEmail helper (currently unused).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/Adapters/Compendium.Adapters.Zitadel/Services/ZitadelUserService.cs Renames the email-hash log placeholder in one Zitadel debug log site.
src/Adapters/Compendium.Adapters.Stripe/Services/StripeBillingService.cs Removes email from Stripe error logs and uses Activity.Current?.Id for correlation.
src/Adapters/Compendium.Adapters.Listmonk/Services/ListmonkNewsletterService.cs Removes email from newsletter subscribe/unsubscribe/update logs; prefers subscriber ID and activity ID.
src/Adapters/Compendium.Adapters.LemonSqueezy/Services/LemonSqueezyBillingService.cs Removes email from LemonSqueezy debug log; uses activity ID for correlation.
src/Adapters/Compendium.Adapters.Shared/Logging/PiiMasking.cs Introduces a masking helper for emails as an anti-regression fallback.
src/Adapters/Compendium.Adapters.Shared/Compendium.Adapters.Shared.csproj Adds a new shared adapters utilities project.
Compendium.sln Adds the new shared adapters project to the solution.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +17 to +28
/// Masks an email for logging: "john.doe@acme.com" → "j***@acme.com".
/// Returns "<empty>" or "<null>" for non-values.
/// Use only when subscriber_id/customer_id is unavailable AND email correlation is required for debugging.
/// </summary>
/// <param name="email">The email to mask.</param>
/// <returns>Masked email, or a placeholder for empty/invalid input.</returns>
public static string MaskEmail(string? email)
{
if (string.IsNullOrWhiteSpace(email)) return "<empty>";
var atIndex = email.IndexOf('@');
if (atIndex <= 0) return "***";
return $"{email[0]}***{email[atIndex..]}";
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XML docs say this returns "" or "" for non-values, but the implementation returns "" for both null and whitespace, and returns "***" for invalid formats (no '@'). Please align the docs with the actual behavior, or update the implementation to distinguish null vs empty (and document the invalid-email case).

Copilot uses AI. Check for mistakes.
// POM-170: debug logs also flow into centralised log stores, so use the
// short hash here as well.
_logger.LogDebug("Getting user by email (hash {EmailHashPrefix})", HashPrefix(email));
_logger.LogDebug("Getting user by email (hash {HashPrefix})", HashPrefix(email));
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This placeholder was renamed to {HashPrefix} here, but the same file still contains log templates using {EmailHashPrefix} (e.g., in CreateUserAsync). That leaves {Email placeholders in adapter logs and contradicts the PR description/verification grep. Please update the remaining templates to use {HashPrefix} as well for consistency and to keep the grep clean.

Copilot uses AI. Check for mistakes.
Refs POM-178. Closes CodeQL alerts #5, #6 (cs/exposure-of-sensitive-information).

`endpoint` was tainted by email values flowing through query strings
(e.g. `subscribers?query=email='{encodedEmail}'`). Strip the query
portion before logging via SanitizeEndpointForLog helper.
Refs POM-178. CodeQL doesn't recognize SanitizeEndpointForLog as a sanitizer
and still flags taint propagation. Replace endpoint-bearing logs with fixed
method-name strings to break the data flow entirely.

Also closes 2 new clear-text-storage alerts on licenseKeyId (same root cause:
endpoint is built from licenseKeyId in the License Get path).
@Pomdapis Pomdapis merged commit 3e3f6e5 into main Apr 25, 2026
5 of 6 checks passed
@Pomdapis Pomdapis deleted the pom-178-gdpr-remove-email-from-logs branch April 25, 2026 05:22
Pomdapis added a commit that referenced this pull request Apr 26, 2026
## Summary

PR #23 incorrectly bumped CHANGELOG to `[1.0.0-preview.2] - 2026-04-26`
with the quality-sweep entries — but tag `v1.0.0-preview.2` was already
cut on **2026-04-25** from a different commit set (PRs #1-7) and
**already published to nuget.org** (`Compendium.Core 1.0.0-preview.2` is
live). Reusing that version was a mistake.

This PR reconciles the CHANGELOG with the published reality and rolls
today's work into a new **preview.3**:

### `[1.0.0-preview.2] - 2026-04-25` — rewritten retroactively

Now matches the auto-generated GitHub release notes for
`v1.0.0-preview.2`:
- **Added** — `Compendium.Adapters.Shared` (PII masking utilities,
introduced in #3).
- **Changed** — Dependabot bumps #4-7, OSS governance scaffolding.
- **Security** — workflow `permissions:` block (#1), tenant log
sanitization (#2), email removal from adapter logs / GDPR (#3).

### `[1.0.0-preview.3] - 2026-04-26` — new

Everything since `v1.0.0-preview.2`:
- **Added** — DocFX site (#17), 5 ADRs (#14), public ROADMAP (#15),
getting-started guide (#20), 4 concept pages (#21), 8 adapter how-to
guides (#22).
- **Changed** — CodeQL Default Setup → `extended` query suite.
- **Security** — `softprops/action-gh-release` pinned to commit SHA
(#16, alert #28 closed).

## Test plan

- [ ] CI green on this PR.
- [ ] After merge, tag `v1.0.0-preview.3` triggers Release workflow
successfully.
- [ ] `Compendium.* @ 1.0.0-preview.3` published on nuget.org.
- [ ] GitHub Release `v1.0.0-preview.3` created with auto-generated
notes.

VK: POM-186 (Code Quality sweep parent).

Co-authored-by: sacha <sacha@scojhconsult.com>
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