Skip to content

fix(web-sources): close crawler DoS vector (#399) - #1252

Merged
DerrickF merged 1 commit into
developfrom
fix/web-crawler-dos-guard
Sep 23, 2026
Merged

DerrickF merged 1 commit into
developfrom
fix/web-crawler-dos-guard

Conversation

@DerrickF

Copy link
Copy Markdown
Contributor

Summary

Closes the web-crawler DoS vector described in #399. Three related fixes so a flood of crawl requests can no longer freeze the container serving all HTTP traffic.

1. Concurrency guard (highest impact)

POST /assistants/{id}/web-sources/crawl now checks list_active_crawls before any write and returns 409 Conflict if a crawl is already running for that assistant. This closes the amplification vector — a script (or a user double-clicking on a slow connection) can no longer launch unbounded simultaneous crawls.

  • The check is DynamoDB-backed, so it works across containers (the process-local _BACKGROUND_CRAWLS set never did — see the issue's Additional Notes).
  • list_active_crawls already self-heals stale running rows, so a crawl whose owning process died is auto-failed and does not wrongly block a new one.
  • Checked before create_document/create_crawl_job, so a rejected request leaves no orphan rows.

2. Offload CPU-bound parsing

trafilatura.extract() and BeautifulSoup() in crawler.py now run via run_in_executor (thread pool) instead of directly on the shared event loop, so parsing a large page can't stall auth/chat/other requests.

3. Offload blocking DynamoDB

Every boto3 table call in crawl_repository.py (put/get/query/update/delete — 9 call sites) now runs via run_in_executor through a small _run_ddb helper. This matters most for increment_counters, which the crawler hits per page. Chose run_in_executor over aioboto3 to avoid a new dependency and match the existing pattern already used for the S3 put.

Scope note

The issue's two Additional Notes (cross-container crawl count, lazy stale-reaping) are effectively addressed by fix #1 using the DynamoDB-backed guard. The S3 upload (_put_markdown) was already offloaded and is unchanged.

Testing

  • New route test: 409 is returned when a crawl is already running, and no document/job/crawler writes happen on rejection.
  • Patched the existing happy-path start-crawl tests for the new list_active_crawls call.
  • 53 passed across test_routes.py, test_crawler.py, test_crawl_repository.py; ruff clean on all three source files.

Fixes #399 (will close manually after merge — Fixes only auto-closes on merge to the default branch).

Three related fixes so a flood of crawl requests can no longer freeze the container:

1. Concurrency guard: POST /assistants/{id}/web-sources/crawl now rejects with
   409 if a crawl is already running for that assistant (checked via
   list_active_crawls before any write, so no orphan document/job rows). This
   is DynamoDB-backed, so the guard is cross-container.
2. Offload CPU-bound parsing: trafilatura.extract() and BeautifulSoup() in the
   crawler now run via run_in_executor instead of blocking the shared event loop.
3. Offload DynamoDB: every boto3 table call in crawl_repository.py (put/get/
   query/update/delete) now runs via run_in_executor (new _run_ddb helper),
   so per-page counter writes no longer stall the loop on network I/O.

Added a route test for the 409 guard (asserts no writes happen on rejection)
and patched the existing happy-path tests for the new list_active_crawls call.
@DerrickF
DerrickF merged commit 7def7d0 into develop Sep 23, 2026
6 checks passed
@DerrickF
DerrickF deleted the fix/web-crawler-dos-guard branch September 23, 2026 18:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant