From be9f8e27023afe1d542488677a24f985edecbf9b Mon Sep 17 00:00:00 2001 From: Anne Beth Andersen <195699472+abla86@users.noreply.github.com> Date: Fri, 25 Sep 2026 22:32:15 +0200 Subject: [PATCH 1/3] fix: contain unexpected DICOM inspection failures --- Program.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Program.cs b/Program.cs index 3408b45..b61491b 100644 --- a/Program.cs +++ b/Program.cs @@ -251,6 +251,18 @@ await repository.AddAuditEventAsync("dicom.inspect", "invalid-dicom", cancellationToken); return Results.BadRequest(new { message = "The request body is not a readable DICOM file." }); } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + } + catch (Exception) + { + await repository.AddAuditEventAsync("dicom.inspect", "processing-error", CancellationToken.None); + return Results.Problem( + statusCode: StatusCodes.Status500InternalServerError, + title: "DICOM inspection failed.", + detail: "The DICOM document could not be processed."); + } }).RequireRateLimiting("api-write"); app.MapGet("/dicom/admin/inspections", async ( From fa5587e32173af80e7c6770ce16d5281fa0158ba Mon Sep 17 00:00:00 2001 From: Anne Beth Andersen <195699472+abla86@users.noreply.github.com> Date: Fri, 25 Sep 2026 22:32:39 +0200 Subject: [PATCH 2/3] fix: prevent audit persistence from crashing API requests --- Program.cs | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/Program.cs b/Program.cs index b61491b..578c031 100644 --- a/Program.cs +++ b/Program.cs @@ -199,13 +199,13 @@ "application/dicom", StringComparison.OrdinalIgnoreCase) != true) { - await repository.AddAuditEventAsync("dicom.inspect", "unsupported-media-type", cancellationToken); + await TryAuditAsync(repository, "dicom.inspect", "unsupported-media-type", cancellationToken); return Results.StatusCode(StatusCodes.Status415UnsupportedMediaType); } if (request.ContentLength is > MaxDicomUploadBytes) { - await repository.AddAuditEventAsync("dicom.inspect", "payload-too-large", cancellationToken); + await TryAuditAsync(repository, "dicom.inspect", "payload-too-large", cancellationToken); return Results.StatusCode(StatusCodes.Status413PayloadTooLarge); } @@ -233,7 +233,7 @@ if (total == 0) { - await repository.AddAuditEventAsync("dicom.inspect", "empty-body", cancellationToken); + await TryAuditAsync(repository, "dicom.inspect", "empty-body", cancellationToken); return Results.BadRequest(new { message = "A DICOM request body is required." }); } @@ -242,13 +242,20 @@ try { var inspection = service.Inspect(buffer); - await repository.AddInspectionAsync(inspection, cancellationToken); - await repository.AddAuditEventAsync("dicom.inspect", "success", cancellationToken); + try + { + await repository.AddInspectionAsync(inspection, cancellationToken); + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + } + await TryAuditAsync(repository, "dicom.inspect", "success", cancellationToken); return Results.Ok(inspection); } catch (FellowOakDicom.DicomFileException) { - await repository.AddAuditEventAsync("dicom.inspect", "invalid-dicom", cancellationToken); + await TryAuditAsync(repository, "dicom.inspect", "invalid-dicom", cancellationToken); return Results.BadRequest(new { message = "The request body is not a readable DICOM file." }); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) @@ -257,7 +264,7 @@ } catch (Exception) { - await repository.AddAuditEventAsync("dicom.inspect", "processing-error", CancellationToken.None); + await TryAuditAsync(repository, "dicom.inspect", "processing-error", CancellationToken.None); return Results.Problem( statusCode: StatusCodes.Status500InternalServerError, title: "DICOM inspection failed.", @@ -274,11 +281,11 @@ { if (!IsAuthorized(request, configuration)) { - await repository.AddAuditEventAsync("dicom.admin.inspections", "unauthorized", cancellationToken); + await TryAuditAsync(repository, "dicom.admin.inspections", "unauthorized", cancellationToken); return Results.Unauthorized(); } - await repository.AddAuditEventAsync("dicom.admin.inspections", "success", cancellationToken); + await TryAuditAsync(repository, "dicom.admin.inspections", "success", cancellationToken); var records = await repository.GetRecentAsync(take ?? 25, cancellationToken); return Results.Ok(records); }).RequireRateLimiting("api-write"); @@ -303,6 +310,22 @@ app.Run(); +static async Task TryAuditAsync(IDicomMetadataRepository repository, string eventType, string outcome, CancellationToken cancellationToken) +{ + try + { + await repository.AddAuditEventAsync(eventType, outcome, cancellationToken); + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + } + catch + { + // Audit persistence must never turn a request into an application failure. + } +} + static bool IsAuthorized(HttpRequest request, IConfiguration configuration) { var configuredKey = configuration["Security:ApiKey"]; From 6a6b5020c405c9e0838e8db688101f16a9749604 Mon Sep 17 00:00:00 2001 From: Anne Beth Andersen <195699472+abla86@users.noreply.github.com> Date: Fri, 25 Sep 2026 22:32:46 +0200 Subject: [PATCH 3/3] fix: reject invalid device ids --- Program.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Program.cs b/Program.cs index 578c031..aa0469d 100644 --- a/Program.cs +++ b/Program.cs @@ -129,6 +129,11 @@ app.MapGet("/devices/{id:int}", (int id, DeviceService service) => { + if (id <= 0) + { + return Results.BadRequest(new { message = "Device ID must be greater than zero." }); + } + var device = service.GetDevice(id); return device is null