fix(SEC-API-19+20): Validate brain search length and limit params
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
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:
@@ -1,4 +1,4 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { Injectable, BadRequestException } from "@nestjs/common";
|
||||
import { EntityType, TaskStatus, ProjectStatus } from "@prisma/client";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
import type { BrainQueryDto, BrainContextDto, TaskFilter, EventFilter, ProjectFilter } from "./dto";
|
||||
@@ -80,6 +80,11 @@ export interface BrainContext {
|
||||
}[];
|
||||
}
|
||||
|
||||
/** Maximum allowed length for search query strings */
|
||||
const MAX_SEARCH_LENGTH = 500;
|
||||
/** Maximum allowed limit for search results per entity type */
|
||||
const MAX_SEARCH_LIMIT = 100;
|
||||
|
||||
/**
|
||||
* @description Service for querying and aggregating workspace data for AI/brain operations.
|
||||
* Provides unified access to tasks, events, and projects with filtering and search capabilities.
|
||||
@@ -97,15 +102,28 @@ export class BrainService {
|
||||
*/
|
||||
async query(queryDto: BrainQueryDto): Promise<BrainQueryResult> {
|
||||
const { workspaceId, entities, search, limit = 20 } = queryDto;
|
||||
if (search && search.length > MAX_SEARCH_LENGTH) {
|
||||
throw new BadRequestException(
|
||||
`Search term must not exceed ${String(MAX_SEARCH_LENGTH)} characters`
|
||||
);
|
||||
}
|
||||
if (queryDto.query && queryDto.query.length > MAX_SEARCH_LENGTH) {
|
||||
throw new BadRequestException(
|
||||
`Query must not exceed ${String(MAX_SEARCH_LENGTH)} characters`
|
||||
);
|
||||
}
|
||||
const clampedLimit = Math.max(1, Math.min(limit, MAX_SEARCH_LIMIT));
|
||||
const includeEntities = entities ?? [EntityType.TASK, EntityType.EVENT, EntityType.PROJECT];
|
||||
const includeTasks = includeEntities.includes(EntityType.TASK);
|
||||
const includeEvents = includeEntities.includes(EntityType.EVENT);
|
||||
const includeProjects = includeEntities.includes(EntityType.PROJECT);
|
||||
|
||||
const [tasks, events, projects] = await Promise.all([
|
||||
includeTasks ? this.queryTasks(workspaceId, queryDto.tasks, search, limit) : [],
|
||||
includeEvents ? this.queryEvents(workspaceId, queryDto.events, search, limit) : [],
|
||||
includeProjects ? this.queryProjects(workspaceId, queryDto.projects, search, limit) : [],
|
||||
includeTasks ? this.queryTasks(workspaceId, queryDto.tasks, search, clampedLimit) : [],
|
||||
includeEvents ? this.queryEvents(workspaceId, queryDto.events, search, clampedLimit) : [],
|
||||
includeProjects
|
||||
? this.queryProjects(workspaceId, queryDto.projects, search, clampedLimit)
|
||||
: [],
|
||||
]);
|
||||
|
||||
// Build filters object conditionally for exactOptionalPropertyTypes
|
||||
@@ -259,10 +277,17 @@ export class BrainService {
|
||||
* @throws PrismaClientKnownRequestError if database query fails
|
||||
*/
|
||||
async search(workspaceId: string, searchTerm: string, limit = 20): Promise<BrainQueryResult> {
|
||||
if (searchTerm.length > MAX_SEARCH_LENGTH) {
|
||||
throw new BadRequestException(
|
||||
`Search term must not exceed ${String(MAX_SEARCH_LENGTH)} characters`
|
||||
);
|
||||
}
|
||||
const clampedLimit = Math.max(1, Math.min(limit, MAX_SEARCH_LIMIT));
|
||||
|
||||
const [tasks, events, projects] = await Promise.all([
|
||||
this.queryTasks(workspaceId, undefined, searchTerm, limit),
|
||||
this.queryEvents(workspaceId, undefined, searchTerm, limit),
|
||||
this.queryProjects(workspaceId, undefined, searchTerm, limit),
|
||||
this.queryTasks(workspaceId, undefined, searchTerm, clampedLimit),
|
||||
this.queryEvents(workspaceId, undefined, searchTerm, clampedLimit),
|
||||
this.queryProjects(workspaceId, undefined, searchTerm, clampedLimit),
|
||||
]);
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user