-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Summary
- Context:
IngestionControllerprovides endpoints for triggering remote web crawling and local directory ingestion. It uses JSR-303 validation annotations (@Min,@Max) on request parameters to enforce safety limits on the scope of these operations. - Bug: The
IngestionControllerclass is missing the@Validatedannotation, which is required by Spring to enable method-level validation for request parameters. - Actual vs. expected: Validation constraints on
@RequestParamarguments are ignored; a user can provide values formaxPagesormaxFilesthat exceed the intended safety limits (e.g., millions of pages). - Impact: An attacker can trigger extremely large, resource-intensive, and potentially costly ingestion runs (remote crawls or local scans), leading to Denial of Service via thread pool exhaustion and disk space depletion.
Code with bug
@RestController
@RequestMapping("/api/ingest")
@PermitAll
@PreAuthorize("permitAll()")
public class IngestionController extends BaseController { // <-- BUG 🔴 Missing @Validated annotation
// ...
@PostMapping
public ResponseEntity<? extends IngestionResponse> ingest(
@RequestParam(name = "maxPages", defaultValue = "100")
@Min(value = 1, message = "maxPages must be at least 1")
@Max(value = MAX_ALLOWED_PAGES, message = "maxPages cannot exceed " + MAX_ALLOWED_PAGES) // <-- BUG 🔴 Not enforced
int maxPages) {Evidence
- Spring Framework Requirements: In Spring Boot, JSR-303 annotations on method parameters (like
@RequestParam,@PathVariable) are only processed if the class is annotated with@org.springframework.validation.annotation.Validated. Without this annotation, Spring does not create the necessary AOP proxy to intercept calls and validate the arguments. - Missing Safety Net: While
LocalDocsDirectoryIngestionServicemanually checks ifmaxFiles <= 0, it does NOT check the upper bound.DocsIngestionService(for remote crawling) has no manual validation ofmaxPagesat all. This means the@Maxlimit defined in the controller is the only thing preventing a massive ingestion run, and it is currently disabled. - Synchronous Execution: The ingestion methods call
docsIngestionService.crawlAndIngest(maxPages)anddocsIngestionService.ingestLocalDirectory(...)synchronously. These methods perform blocking IO (network requests, disk writes). A single request with an extremely highmaxPagesvalue will tie up a Tomcat worker thread for a long duration, making it trivial to exhaust the thread pool. - Incorrect Error Handling Path: The controller contains a redundant
@ExceptionHandler(IllegalArgumentException.class). If validation were working correctly, it would throwConstraintViolationException, notIllegalArgumentException. Furthermore, the methods containcatch (RuntimeException runtimeException)blocks that return500 INTERNAL SERVER ERROR, which would catch any validation exceptions (if enabled) and hide the fact that they are client-side errors (which should be400 BAD REQUEST).
Why has this bug gone undetected?
The application appears to be in an early or development stage where ingestion is triggered with default values or small increments. Since the code does not crash when receiving a large number (it just proceeds to crawl as many pages as it can find), the lack of enforcement is only apparent when monitoring server resources or LLM costs during a large-scale or malicious request. Additionally, the presence of the validation annotations gives a false sense of security to developers reviewing the code.
Recommended fix
Add the @Validated annotation to the IngestionController class to enable constraint enforcement for method parameters. Additionally, remove the broad catch (RuntimeException) blocks in the controller methods and rely on the @ExceptionHandler (updated to handle ConstraintViolationException) to return consistent 400 BAD REQUEST responses for all validation failures.
@RestController
@RequestMapping("/api/ingest")
@Validated // <-- FIX 🟢
@PermitAll
@PreAuthorize("permitAll()")
public class IngestionController extends BaseController {