fix(security): audit remediation batch A — validation, rate limiting, sessions
Implements AUDIT_REPORT.md items H-1, H-2, M-1, M-4, M-6, L-3: - H-1: enable FluentValidation auto-validation — the registered validators (incl. Confirmed-required-for-destructive) now actually execute; invalid DTOs 400 at the boundary instead of reaching services. - H-2: ASP.NET Core rate limiting — global per-user/per-IP fixed window (300/min default) + stricter 'auth' (10/min) and 'expensive' (20/min: export, unsubscribe, AI) policies; config-driven; 429 with no queue. - M-1: absolute session lifetime (30d default) — an issued-at stamp set at sign-in and checked in OnValidatePrincipal, so a stolen cookie can no longer slide-renew forever. Pre-existing sessions re-login once. - M-4: remove the guessable default DB password from appsettings; startup fails fast with a clear message when the connection string has no password (compose/staging inject the real one). - M-6: matching tenant query filter on EmailLabel (via Email navigation) — clears the long-standing EF boot warning and closes the join-row leak window. - L-3: SMTP skip-notice logging downgraded to Debug (recipient address is PII-ish). Tests: 6 new (400-on-invalid x2, 429 auth rate limit via a test auth scheme, session-lifetime x3, EmailLabel cross-user invisibility). Full suite: 48/48 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,7 @@ public class SmtpEmailSender : IEmailSender
|
||||
{
|
||||
if (!IsEnabled)
|
||||
{
|
||||
_logger.LogInformation("SMTP not configured; skipping email \"{Subject}\" to {To}", subject, toAddress);
|
||||
_logger.LogDebug("SMTP not configured; skipping email \"{Subject}\" to {To}", subject, toAddress);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,10 @@ public class AppDbContext : DbContext, IAppDbContext
|
||||
modelBuilder.Entity<MailDomain>().HasQueryFilter(e => CurrentUserId == Guid.Empty || e.UserId == CurrentUserId);
|
||||
modelBuilder.Entity<Attachment>().HasQueryFilter(e => CurrentUserId == Guid.Empty || e.UserId == CurrentUserId);
|
||||
modelBuilder.Entity<Label>().HasQueryFilter(e => CurrentUserId == Guid.Empty || e.UserId == CurrentUserId);
|
||||
// AUDIT M-6: EmailLabel is the required end of a relationship with the filtered Email
|
||||
// entity; without a matching filter EF warns on boot and joins could surface rows whose
|
||||
// parent is filtered out. Filter via the Email navigation so the pair is consistent.
|
||||
modelBuilder.Entity<EmailLabel>().HasQueryFilter(el => CurrentUserId == Guid.Empty || el.Email!.UserId == CurrentUserId);
|
||||
modelBuilder.Entity<SyncState>().HasQueryFilter(e => CurrentUserId == Guid.Empty || e.UserId == CurrentUserId);
|
||||
modelBuilder.Entity<AnalyticsAggregate>().HasQueryFilter(e => CurrentUserId == Guid.Empty || e.UserId == CurrentUserId);
|
||||
modelBuilder.Entity<WidgetLayout>().HasQueryFilter(e => CurrentUserId == Guid.Empty || e.UserId == CurrentUserId);
|
||||
|
||||
Reference in New Issue
Block a user