From d4c486681ad51424bfd33545d518e87193ffbc56 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Mon, 9 Nov 2026 22:52:07 +0100 Subject: [PATCH] fix(security): keep imported mail subjects out of the admin audit --- .../AdminProcessingControllerTests.cs | 82 +++++++++++++++++- .../Controllers/AdminAuditController.cs | 3 +- docs/security/ADMIN_INFORMATION_ISOLATION.md | 29 +++++++ .../admin-information-isolation/green.trx.gz | Bin 0 -> 13075 bytes .../admin-information-isolation/red.trx.gz | Bin 0 -> 1654 bytes 5 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 docs/security/ADMIN_INFORMATION_ISOLATION.md create mode 100644 docs/security/evidence/admin-information-isolation/green.trx.gz create mode 100644 docs/security/evidence/admin-information-isolation/red.trx.gz diff --git a/JobTrackerApi.Tests/AdminProcessingControllerTests.cs b/JobTrackerApi.Tests/AdminProcessingControllerTests.cs index 204d50b0..754ecdf6 100644 --- a/JobTrackerApi.Tests/AdminProcessingControllerTests.cs +++ b/JobTrackerApi.Tests/AdminProcessingControllerTests.cs @@ -159,6 +159,82 @@ public sealed class AdminProcessingControllerTests Assert.Equal(expected, (await client.GetAsync(route)).StatusCode); } + [Fact] + public async Task Audit_log_keeps_mail_subjects_in_the_owner_record() + { + await using var host = await Host.Start(); + const string privateSubject = "Synthetic private correspondence subject"; + const string operationalNote = "Synthetic workflow status change"; + await using (var scope = host.Services.CreateAsyncScope()) + { + var db = scope.ServiceProvider.GetRequiredService(); + db.JobEvents.AddRange( + new JobEvent { JobApplicationId = 2, Type = "ReplyReceived", NewValue = "True:2026-11-03", Note = privateSubject, At = DateTime.UtcNow }, + new JobEvent { JobApplicationId = 2, Type = "StatusChanged", OldValue = "Applied", NewValue = "Interview", Note = operationalNote, At = DateTime.UtcNow }); + await db.SaveChangesAsync(); + } + + using var anonymous = host.Client(null); + using var ordinary = host.Client("owner"); + foreach (var (client, status) in new[] { (anonymous, HttpStatusCode.Unauthorized), (ordinary, HttpStatusCode.Forbidden) }) + { + using var response = await client.GetAsync("/api/admin/audit"); + Assert.Equal(status, response.StatusCode); + Assert.DoesNotContain(privateSubject, await response.Content.ReadAsStringAsync()); + } + + using var admin = host.Client("admin"); + var audit = await admin.GetFromJsonAsync("/api/admin/audit"); + var events = audit.EnumerateArray().ToDictionary(item => item.GetProperty("type").GetString()!); + Assert.DoesNotContain(privateSubject, audit.GetRawText()); + var reply = events["ReplyReceived"]; + Assert.Equal(JsonValueKind.Null, reply.GetProperty("note").ValueKind); + Assert.Equal("True:2026-11-03", reply.GetProperty("newValue").GetString()); + Assert.Equal(2, reply.GetProperty("jobApplicationId").GetInt32()); + Assert.Equal(operationalNote, events["StatusChanged"].GetProperty("note").GetString()); + + await using var verifyScope = host.Services.CreateAsyncScope(); + var verifyDb = verifyScope.ServiceProvider.GetRequiredService(); + Assert.Equal(privateSubject, await verifyDb.JobEvents.IgnoreQueryFilters().Where(item => item.Type == "ReplyReceived").Select(item => item.Note).SingleAsync()); + } + + [Fact] + public async Task User_polling_does_not_expose_another_owners_notifications_or_processing_records() + { + await using var host = await Host.Start(); + var foreignNoticeId = Guid.NewGuid(); + const string foreignNotice = "Synthetic notice for the other owner"; + await using (var scope = host.Services.CreateAsyncScope()) + { + var db = scope.ServiceProvider.GetRequiredService(); + db.UserNotifications.AddRange( + new UserNotification { Id = Guid.NewGuid(), OwnerUserId = "owner", Kind = "operation_failed", Title = "Operation failed", Message = "Synthetic owner notice", CreatedAtUtc = DateTime.UtcNow }, + new UserNotification { Id = foreignNoticeId, OwnerUserId = "other", Kind = "operation_failed", Title = "Operation failed", Message = foreignNotice, CreatedAtUtc = DateTime.UtcNow }); + await db.SaveChangesAsync(); + } + + using var owner = host.Client("owner"); + var notices = await owner.GetFromJsonAsync("/api/notifications?owner=other"); + Assert.Equal("Synthetic owner notice", Assert.Single(notices.EnumerateArray()).GetProperty("message").GetString()); + var unread = await owner.GetFromJsonAsync("/api/notifications/unread-count?owner=other"); + Assert.Equal(1, unread.GetProperty("count").GetInt32()); + Assert.Equal(HttpStatusCode.NotFound, (await owner.PostAsync($"/api/notifications/{foreignNoticeId}/read", null)).StatusCode); + Assert.Equal(HttpStatusCode.NotFound, (await owner.DeleteAsync($"/api/notifications/{foreignNoticeId}")).StatusCode); + Assert.Equal(HttpStatusCode.NotFound, (await owner.GetAsync($"/api/operations/{host.ForeignSubjectOperationId}")).StatusCode); + var ownOperation = await owner.GetStringAsync($"/api/operations/{host.OperationId}"); + foreach (var internalValue in new[] { "SECRET_TOKEN", "SECRET_EXCEPTION", "SECRET_RESULT", "SECRET_CV_CONTENT" }) + Assert.DoesNotContain(internalValue, ownOperation); + + using var other = host.Client("other"); + notices = await other.GetFromJsonAsync("/api/notifications"); + Assert.Equal(foreignNotice, Assert.Single(notices.EnumerateArray()).GetProperty("message").GetString()); + await using var verifyScope = host.Services.CreateAsyncScope(); + var verifyDb = verifyScope.ServiceProvider.GetRequiredService(); + var notice = await verifyDb.UserNotifications.IgnoreQueryFilters().SingleAsync(item => item.Id == foreignNoticeId); + Assert.Null(notice.ReadAtUtc); + Assert.Null(notice.DismissedAtUtc); + } + [Fact] public async Task Admin_projection_filters_owners_and_never_returns_raw_processing_contents_or_lease_data() { @@ -216,7 +292,9 @@ public sealed class AdminProcessingControllerTests var builder = WebApplication.CreateBuilder(); builder.Logging.ClearProviders(); builder.WebHost.UseUrls("http://127.0.0.1:0"); builder.Services.AddControllers().AddApplicationPart(typeof(AuthController).Assembly); - builder.Services.AddAuthentication("test").AddScheme("test", _ => { }); + builder.Services.AddAuthentication("test") + .AddScheme("test", _ => { }) + .AddScheme("local", _ => { }); builder.Services.AddAuthorization(); builder.Services.AddHttpContextAccessor(); builder.Services.AddRateLimiter(options => { @@ -226,6 +304,8 @@ public sealed class AdminProcessingControllerTests _ => new FixedWindowRateLimiterOptions { PermitLimit = permits, Window = TimeSpan.FromMinutes(1), QueueLimit = 0 })); }); builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddSingleton(TimeProvider.System); builder.Services.AddDbContext(options => options.UseSqlite($"Data Source={Path.Combine(directory, "processing.db")};Pooling=False")); var app = builder.Build(); diff --git a/JobTrackerApi/Controllers/AdminAuditController.cs b/JobTrackerApi/Controllers/AdminAuditController.cs index e8817d24..7468db9a 100644 --- a/JobTrackerApi/Controllers/AdminAuditController.cs +++ b/JobTrackerApi/Controllers/AdminAuditController.cs @@ -51,7 +51,8 @@ public sealed class AdminAuditController : ControllerBase e.Type, e.OldValue, e.NewValue, - e.Note, + // Imported replies keep the mailbox subject in the owner's timeline. + Note = e.Type == "ReplyReceived" ? null : e.Note, e.At, e.JobApplicationId }) diff --git a/docs/security/ADMIN_INFORMATION_ISOLATION.md b/docs/security/ADMIN_INFORMATION_ISOLATION.md new file mode 100644 index 00000000..21daf272 --- /dev/null +++ b/docs/security/ADMIN_INFORMATION_ISOLATION.md @@ -0,0 +1,29 @@ +# Admin information isolation, offline increment + +9 November 2026. Base `de3d5031c8208bc3841a528ae100ec2984d68501` on `codex/security-offline-preflight`. Compared the audit, Gmail, operations and fixture owners with A3 checkpoint `91e5a6bf`; their files were identical before this patch. Testing uses ephemeral loopback Kestrel and SQLite with synthetic owners and a test authentication scheme. No real mailbox, model, credential, provider, staging host or production service was accessed. + +## Confirmed, privileged audit projection + +The Gmail import creates a `ReplyReceived` event with the original mail subject in `JobEvent.Note`. The Admin-only Audit endpoint reads events across owners and previously sent that subject to administrators in the generic note field. A synthetic reply proved the subject was returned to an authenticated administrator. Anonymous and ordinary users were denied in the same test. This is excessive visibility to an existing administrative role, with a mailbox-import precondition. The fixture did not show an ordinary-user role bypass. + +The read projection now omits `Note` for `ReplyReceived`. This also covers historical events without touching stored rows. The event type, response metadata, application association and ordinary status notes remain available. The owner's original event is preserved. Roles, undo/restore, notification producers and persisted data are unchanged. + +## Covered boundaries + +| Boundary | Evidence | +| --- | --- | +| Admin Audit | Loopback HTTP verifies 401/403 and the redacted Admin response. The allowed event metadata and a non-private status note remain. Stored owner subject still matches the synthetic input. | +| Admin Processing | Existing HTTP tests verify all three endpoints deny ordinary/anonymous callers, keep role and bounded rate enforcement, and omit raw CV content, file paths and lease data from allowed diagnostics. | +| User notifications | HTTP list/count ignore a forged owner query. A foreign notification ID returns 404 for mark-read and dismiss. The other owner still sees their own notice. Relational state confirms neither mutation occurred. | +| User operations | The owner cannot retrieve the other owner's operation by ID. The owner's own detail omits raw failure text, lease token, result reference and CV content. Public task stage/category remain part of the existing contract. | +| Pollers and producers | Reviewed EF owner filters and operation, saved-search and interrupted-mail producers. These write owner-bound user messages. No admin event producer was identified in the examined notification store. System and Users server role requirements were inspected and covered by existing authorization posture tests. | +| Public liveness/readiness | Source shows the existing minimal liveness version/status and readiness status contracts. Neither substitutes for the role-gated system diagnostic payload. No live checks run. | + +The red test failed once on the original HTTP response. The final four selected suites passed 62 cases, with zero failures/skips and exit 0. Pinned .NET 9.0.317, locked restore from the local cache and Release tests with `--no-restore`. Dependency advisory checks were not run during the offline restore. + +- [Red TRX](evidence/admin-information-isolation/red.trx.gz), uncompressed SHA-256 `3795be6331eb68ff9fef3648d1fea3d938f0333db7c755984eb5705c09f1cb56`. +- [Green TRX](evidence/admin-information-isolation/green.trx.gz), uncompressed SHA-256 `a46e1c00dbf0e486495bce7a11524fe5b75d9975788e35b63f8e82c074eaeaf6`. + +## Open + +Broader audit minimisation, administrative actions, email and realtime delivery boundaries, actual MariaDB and integrated role/session/deployment checks remain open. The Codex Security plugin is present. Formal baseline, diff/deep, integrated and staging scan activities were **NOT RUN** in this independent offline increment. Source and fixture results do not satisfy those later gates. No support-access or recovery permissions changed. [MFA recovery proposal](MFA_RECOVERY_PROPOSAL.md) diff --git a/docs/security/evidence/admin-information-isolation/green.trx.gz b/docs/security/evidence/admin-information-isolation/green.trx.gz new file mode 100644 index 0000000000000000000000000000000000000000..314f0f410e60686ed17320c84c7865568b1140f9 GIT binary patch literal 13075 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 literal 0 HcmV?d00001 diff --git a/docs/security/evidence/admin-information-isolation/red.trx.gz b/docs/security/evidence/admin-information-isolation/red.trx.gz new file mode 100644 index 0000000000000000000000000000000000000000..2db2b416198c524785a065bad9a32dd070e0d6f9 GIT binary patch literal 1654 zcmV-+28sC}iwFP!000003guW`ZsfQTeqUhkKxl!$gI(&EQcIm~Z@jh#n8l2D;r7nU z_MkzLr8dWEvO`jy_62f*93~HW2#}-X0(*r8_2X`@+m3A{lLTq|L87XPRbLgWKIxag z{%!K@W>uqW&_*aZ^&&s?5J;xDkjtsp*b3*~w})STF}VO^&l`yZpL&oLRa}M@<`H3- zB$VJXiWsJ85tqP=l9R$ilCEIt9V;o7{=@O_hd!a{wLpcfjL z(%eH#t=5eN-R(Jm<{{qbzRJRI?EYgvP187xqP|dEG48K`Fpsl1$%YC5$sd;`@ofpZ z=r3;8F!iE@aaQmGb6%#HRFq&^j5y{+mNHhcjFZwssS(i&HyM{@*|T^k;OiRt6Yg3W@hXR zq9`wJ^Z0)kdy#jsV~o+Mux+hX?ATha5_xMR{3dTGT5p+?B0RBR2;dO zzoW*DrhjAkIylbn!m%&eEgY27wFT{_R5vp_N}%*O2sVurHh6D(k|{yQfdtEx2D)B* zh}G1XmrnPAiN2twf_rCShyMJw@LTv#rPTs%Pr|Tt^Gr7LLb~+43BJqacKvR>cBbxx z;TC!%`yPPKlywb~BKNE-_~-0=-|$ggqqmB3;HclDvl=Oz&({5(n9iEjit3wt%xCcAST)juHpnVV>!}wFNdULMht|{>cj;JstJ#UT zF4`_yu>#fD_L`PTw|b$6gk(yxx-sG!+J(;0ca1Cbqa8u1+R~?G-N{L1yLQk&=fb## z25eaPM73;RQNoUm*0A04N3E6`jOlg+*}cx)zE<34U6YyRKQ#96=Z&j$UxGzFe+~UG z{D8L0?`LgmKkAM%x{m$akH5}}f)kR`h@yjx;8IxRpPgQy!Vmpupi}#d7s&^N(1{D~ z8alSM=ZHU~ANf(Vs}GK7Jx@P&`ZLGO+s)Cl^b>#7>bu_yY3bueUqD(PH~251w2yD` ze8T%Q#~t5kiOl$E$o)r&+d-5U--NHmpO35lPk>V7|BawLn^r$!w(V!H+Ydc$H+rO> zIC9>0-GOD?CE(|cbOe2v!rPSxKR(-^gB$3Zok literal 0 HcmV?d00001