Skip to content

[Detail Bug] Missing @Validated in IngestionController disables request param validation, enabling unbounded ingestion #37

@detail-app

Description

@detail-app

Summary

  • Context: IngestionController provides 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 IngestionController class is missing the @Validated annotation, which is required by Spring to enable method-level validation for request parameters.
  • Actual vs. expected: Validation constraints on @RequestParam arguments are ignored; a user can provide values for maxPages or maxFiles that 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

  1. 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.
  2. Missing Safety Net: While LocalDocsDirectoryIngestionService manually checks if maxFiles <= 0, it does NOT check the upper bound. DocsIngestionService (for remote crawling) has no manual validation of maxPages at all. This means the @Max limit defined in the controller is the only thing preventing a massive ingestion run, and it is currently disabled.
  3. Synchronous Execution: The ingestion methods call docsIngestionService.crawlAndIngest(maxPages) and docsIngestionService.ingestLocalDirectory(...) synchronously. These methods perform blocking IO (network requests, disk writes). A single request with an extremely high maxPages value will tie up a Tomcat worker thread for a long duration, making it trivial to exhaust the thread pool.
  4. Incorrect Error Handling Path: The controller contains a redundant @ExceptionHandler(IllegalArgumentException.class). If validation were working correctly, it would throw ConstraintViolationException, not IllegalArgumentException. Furthermore, the methods contain catch (RuntimeException runtimeException) blocks that return 500 INTERNAL SERVER ERROR, which would catch any validation exceptions (if enabled) and hide the fact that they are client-side errors (which should be 400 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 {

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdetail

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions