Files
Inboxintel/docs/specs/feature-privacy-monitor.md
T
cesnimda be6cbf90d7 docs: full specs for automation engine, sender policy, activity log, privacy monitor, UI overhaul
Clean.Email-parity feature build-out plus a Stripe/Notion-style UI rebuild on
Tailwind + shadcn-style primitives. Locks the hybrid-automation, Gmail-only,
light+dark, incremental-rollout decisions and lays out the backend/frontend
build sequence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 23:04:52 +02:00

4.1 KiB

Spec: Privacy Monitor (data-breach checking)

Checks the user's email address (and optionally addresses they've corresponded with) against known data breaches. Spec'd around Have I Been Pwned (HIBP), shipped behind a config flag that stays off until an API key is provided — zero cost until then.

1. Configuration

PrivacyOptions (new, Configuration/Options.cs):

public class PrivacyOptions
{
    public const string SectionName = "Privacy";
    public bool Enabled { get; set; } = false;     // master flag
    public string Provider { get; set; } = "Hibp";  // "Hibp" | "None"
    public string? HibpApiKey { get; set; }          // required for Hibp
    public int CacheHours { get; set; } = 24;        // don't hammer the API
}

appsettings.json gains a Privacy section with Enabled: false, empty key. IsEnabled => Enabled && Provider == "Hibp" && !string.IsNullOrWhiteSpace(HibpApiKey).

2. Provider abstraction

public record BreachDto(string Name, string Title, string Domain, DateOnly BreachDate,
                        IReadOnlyList<string> DataClasses, bool IsVerified, string? Description);

public interface IBreachProvider
{
    bool IsEnabled { get; }
    /// <summary>Breaches for an address; empty list if clean; throws only on hard errors.</summary>
    Task<IReadOnlyList<BreachDto>> CheckAsync(string emailAddress, CancellationToken ct = default);
}
  • HibpBreachProviderGET https://haveibeenpwned.com/api/v3/breachedaccount/{account}?truncateResponse=false, header hibp-api-key, a descriptive user-agent. Handle: 404 = no breaches (return empty), 401 = misconfig (log, treat as disabled), 429 = rate-limited (respect Retry-After, return cached/empty). Uses a named HttpClient "hibp" with a sane timeout.
  • NullBreachProviderIsEnabled => false, returns empty. Registered when the flag is off (mirrors the NullAiProvider pattern in DependencyInjection).

3. Service

public interface IPrivacyService
{
    bool IsEnabled { get; }
    /// <summary>Check the signed-in user's own address. Cached per PrivacyOptions.CacheHours.</summary>
    Task<PrivacyReportDto> CheckSelfAsync(Guid userId, CancellationToken ct = default);
}

public record PrivacyReportDto(string Address, bool Checked, int BreachCount,
                               IReadOnlyList<BreachDto> Breaches, DateTimeOffset? LastCheckedUtc);
  • Resolve the user's address from User/identity.
  • Cache the last result + timestamp (new User.LastBreachCheckUtc + a small BreachCheck/JSON column, or a dedicated table if we later check multiple addresses). For v1, store on User: LastBreachCheckUtc, BreachCountCached.
  • Respect CacheHours: return cached unless stale or force requested.

4. API (PrivacyController : ApiControllerBase)

Method Route Purpose
GET /privacy/status { enabled } so the UI can hide the feature when off
GET /privacy/self cached report for the signed-in user
POST /privacy/self/refresh force a fresh check (rate-limit aware)

All scoped to UserId. We only check the authenticated user's own address in v1 — never arbitrary addresses (avoids turning the app into a breach-lookup tool for others).

5. Frontend

  • Privacy page (/app/privacy), hidden from nav when GET /privacy/status is disabled.
  • Shows: address checked, breach count, and a card per breach (title, date, what leaked, verified badge), plus a "Re-check" button and "what this means / next steps" guidance.
  • Dashboard widget (optional): a small "Privacy" tile with breach count + link, also hidden when disabled.

6. Security & safety

  • Only the authenticated user's own address is ever checked (no lookup of others).
  • API key read from config/secrets, never logged, never sent to the client.
  • Feature fully inert (endpoints return { enabled:false }, nav hidden) until a key is set.
  • Rate-limit/backoff respected; failures degrade to cached/empty, never crash.
  • HIBP responses cached to minimize external calls and avoid leaking usage patterns.