From eace08f3516068f4904a90c3ac884c774540f2d6 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Fri, 2 Oct 2026 20:16:00 -0400 Subject: [PATCH] fix(server): reject NUL characters in nest query search SQLite LIKE stops matching at an embedded NUL, so search=Alpha%00x behaved like 'Alpha' followed by a wildcard and returned rows that do not contain the whole search text. The shared query validation now rejects NUL, so the server answers 400 and the client throws before sending. --- OpenNest.Data/NestQuery.cs | 3 +++ OpenNest.Server.Tests/NestQueryTests.cs | 2 ++ OpenNest.Tests/Data/NestStorageTests.cs | 1 + docs/nest-storage.md | 3 ++- 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/OpenNest.Data/NestQuery.cs b/OpenNest.Data/NestQuery.cs index 40f207a..8916643 100644 --- a/OpenNest.Data/NestQuery.cs +++ b/OpenNest.Data/NestQuery.cs @@ -42,6 +42,9 @@ public sealed class NestQuery return $"limit must be between 1 and {MaxLimit}."; if (NormalizedSearch.Length > MaxSearchLength) return $"search must be at most {MaxSearchLength} characters."; + // SQLite LIKE stops at an embedded NUL, which would turn the rest of the text into a wildcard. + if (NormalizedSearch.Contains('\0')) + return "search must not contain NUL characters."; return null; } } diff --git a/OpenNest.Server.Tests/NestQueryTests.cs b/OpenNest.Server.Tests/NestQueryTests.cs index 9c4e7e5..4c424eb 100644 --- a/OpenNest.Server.Tests/NestQueryTests.cs +++ b/OpenNest.Server.Tests/NestQueryTests.cs @@ -201,6 +201,8 @@ public sealed class NestQueryTests : IDisposable [InlineData("limit=1&limit=2")] [InlineData("serach=beta")] [InlineData("status=quote")] + [InlineData("search=Alpha%00not-present")] + [InlineData("search=%00")] public async Task Query_InvalidParameters_Return400WithoutItems(string queryString) { Seed(3); diff --git a/OpenNest.Tests/Data/NestStorageTests.cs b/OpenNest.Tests/Data/NestStorageTests.cs index a3b44a3..1a41586 100644 --- a/OpenNest.Tests/Data/NestStorageTests.cs +++ b/OpenNest.Tests/Data/NestStorageTests.cs @@ -247,6 +247,7 @@ public class RemoteNestRepositoryTests new NestQuery { Limit = NestQuery.MaxLimit + 1 }, new NestQuery { Search = new string('x', NestQuery.MaxSearchLength + 1) }, new NestQuery { Sort = (NestSortField)99 }, + new NestQuery { Search = "Alpha\0not-present" }, }; [Fact] diff --git a/docs/nest-storage.md b/docs/nest-storage.md index ece0875..8ee1917 100644 --- a/docs/nest-storage.md +++ b/docs/nest-storage.md @@ -67,7 +67,8 @@ outside the shop network without adding one. `GET /api/nests/query` filters, orders and pages in SQLite, so a client receives only the requested page of metadata (never archive bytes): -- `search` (optional): trimmed, at most 200 characters; blank means no filter. +- `search` (optional): trimmed, at most 200 characters, no NUL characters; + blank means no filter. Case-insensitive substring of the whole text in `name`, `customer`, `material`, `madeBy`, `comments` or the status (`ToBeCut` or the display name `To Be Cut`). `%`, `_` and `\` are literal. Dates and numbers are not matched as text.