fix(SEC-API-19+20): Validate brain search length and limit params
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

- Add @MaxLength(500) to BrainQueryDto.query and BrainQueryDto.search fields
- Create BrainSearchDto with validated q (max 500 chars) and limit (1-100) fields
- Update BrainController.search to use BrainSearchDto instead of raw query params
- Add defensive validation in BrainService.search and BrainService.query methods:
  - Reject search terms exceeding 500 characters with BadRequestException
  - Clamp limit to valid range [1, 100] for defense-in-depth
- Add comprehensive tests for DTO validation and service-level guards
- Update existing controller tests for new search method signature

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-06 13:29:03 -06:00
parent ef1f1eee9d
commit 17cfeb974b
6 changed files with 299 additions and 30 deletions

View File

@@ -250,39 +250,33 @@ describe("BrainController", () => {
});
describe("search", () => {
it("should call service.search with parameters", async () => {
const result = await controller.search("test query", "10", mockWorkspaceId);
it("should call service.search with parameters from DTO", async () => {
const result = await controller.search({ q: "test query", limit: 10 }, mockWorkspaceId);
expect(mockService.search).toHaveBeenCalledWith(mockWorkspaceId, "test query", 10);
expect(result).toEqual(mockQueryResult);
});
it("should use default limit when not provided", async () => {
await controller.search("test", undefined as unknown as string, mockWorkspaceId);
it("should use default limit when not provided in DTO", async () => {
await controller.search({ q: "test" }, mockWorkspaceId);
expect(mockService.search).toHaveBeenCalledWith(mockWorkspaceId, "test", 20);
});
it("should cap limit at 100", async () => {
await controller.search("test", "500", mockWorkspaceId);
it("should handle empty search DTO", async () => {
await controller.search({}, mockWorkspaceId);
expect(mockService.search).toHaveBeenCalledWith(mockWorkspaceId, "test", 100);
expect(mockService.search).toHaveBeenCalledWith(mockWorkspaceId, "", 20);
});
it("should handle empty search term", async () => {
await controller.search(undefined as unknown as string, "10", mockWorkspaceId);
it("should handle undefined q in DTO", async () => {
await controller.search({ limit: 10 }, mockWorkspaceId);
expect(mockService.search).toHaveBeenCalledWith(mockWorkspaceId, "", 10);
});
it("should handle invalid limit", async () => {
await controller.search("test", "invalid", mockWorkspaceId);
expect(mockService.search).toHaveBeenCalledWith(mockWorkspaceId, "test", 20);
});
it("should return search result structure", async () => {
const result = await controller.search("test", "10", mockWorkspaceId);
const result = await controller.search({ q: "test", limit: 10 }, mockWorkspaceId);
expect(result).toHaveProperty("tasks");
expect(result).toHaveProperty("events");