Skip to content

FIX: Bound backend request sizes and field lengths - #2960

Merged
varunj-msft merged 4 commits into
microsoft:mainfrom
varunj-msft:varunj-msft/10765-Bound-Backend-Request-Inputs
Oct 6, 2026
Merged

varunj-msft merged 4 commits into
microsoft:mainfrom
varunj-msft:varunj-msft/10765-Bound-Backend-Request-Inputs

Conversation

@varunj-msft

@varunj-msft varunj-msft commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Description

The backend accepted request bodies, strings, lists, and maps of any size, silently dropped label filters without a key:value separator, and returned 500 for turn filters too large for the database.

  • middleware/request_size.py: the backend reads at most 100 MiB of a request body (413, from Content-Length or while streaming) and rejects URLs over 8 KiB (414). Both 413 paths return the same RFC 7807 problem response (RequestTooLargeError plus a handler in register_error_handlers). The middleware is registered innermost so the streamed 413 reaches the route as an HTTPException; CORS stays outside it so browsers can read 413/414.
  • models/common.py: limits applied to request models and route path/query parameters (identifiers 256, cursors 1024, free text such as objectives 100,000, labels up to 100 entries with keys 128 and values 1024, lists 100, configuration and initializer file contents 1,048,576 characters). Prompt content (message pieces, system prompts, converter preview input) and free-form values (metadata and parameter values) are limited only by the body size so long prompts and base64 media keep working.
  • The limits shared with scenario requests (list items, identifiers, label keys and values) are defined once in pyrit/models/request_limits.py (per review). pyrit/models/catalog/scenario.py uses them for run and estimate requests, and backend/models/common.py imports them; body, URL, cursor, and text limits stay in the backend. Technique tokens allow converter modifiers (technique:converter.<name>:...) up to 4,096 characters.
  • Label filters are validated by validate_label_filter: a : separator is required and the stripped key and value must fit the label limits (422 with a readable message); parse_label_query_params reuses it instead of dropping malformed filters.
  • min_turns / max_turns get an upper bound of 10,000 and cutoff_index must be non-negative.
  • README "Request Limits" subsection.

Coordination:

Tests and Documentation

  • tests/unit/backend/test_request_size.py (declared and streamed 413 through the real app with identical bodies), test_common_models.py, test_api_routes.py, tests/unit/models/test_scenario_request.py.
  • python -m pytest tests/unit/backend tests/unit/models -n 4 — 3562 passed, 4 skipped.
  • ruff format / ruff check — passed; ty check pyrit/ — no new diagnostics; pre-commit hooks pass.
  • build_and_test workflow run on the fork at 7c7afc8: pre-commit on ubuntu/windows/macOS and make unit-test-junit on ubuntu/windows/macOS x Python 3.11-3.14 x dev/dev_all — all 35 jobs passed.
  • Live backend at 92fe5c7 (the follow-up commit only moves the shared limits; the values are unchanged): 25/25 checks (413 declared and streamed over a real socket with the same problem body, 414, 422 limits including label key/value limits, a 54k-character objective, a ~7 MB base64 image and a 200k-character text piece accepted, CORS preflight, request-id and security headers on 413).
  • Docs: README "Request Limits". No notebooks changed.

The backend accepted request bodies, strings, lists, and maps of any size,
silently dropped label filters without a key:value separator, and returned
500 for turn filters too large for the database.

Request bodies over 100 MiB now receive 413 with the same problem response
whether the size comes from Content-Length or is reached while the body
streams, and URLs over 8 KiB receive 414. Identifiers, names, labels, filters,
cursors, configuration files, and initializer scripts have length or item
limits shared from the backend common models, and the scenario run requests
get the same limits inline (technique tokens allow converter modifiers).
Values over a limit, label filters without a separator or with a key or value
over the label limits, negative cutoff indexes, and turn filters above 10,000
receive 422.

Prompt content (message pieces, system prompts, and converter preview input)
and free-form values (prompt metadata and parameter values) are limited only
by the body size, so long prompts and base64 media keep working.
Comment thread pyrit/models/catalog/scenario.py Outdated
The scenario request models duplicated the backend's identifier, list, and
label limits because pyrit.models cannot import from the backend, and a test
kept the two copies equal. The shared limits now live in
pyrit.models.request_limits; the scenario models and the backend import them.
HTTP body and URL limits stay in the backend.
@varunj-msft
varunj-msft added this pull request to the merge queue Oct 5, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 5, 2026
@varunj-msft
varunj-msft added this pull request to the merge queue Oct 5, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to no response for status checks Oct 5, 2026
@varunj-msft
varunj-msft added this pull request to the merge queue Oct 6, 2026
Merged via the queue into microsoft:main with commit fc52740 Oct 6, 2026
59 of 77 checks passed
@varunj-msft
varunj-msft deleted the varunj-msft/10765-Bound-Backend-Request-Inputs branch October 6, 2026 00:40
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.

2 participants