chore(deps): bump starlette from 0.49.1 to 1.3.1#3116
Conversation
Bumps [starlette](https://github.com/Kludex/starlette) from 0.49.1 to 1.3.1. - [Release notes](https://github.com/Kludex/starlette/releases) - [Changelog](https://github.com/Kludex/starlette/blob/main/docs/release-notes.md) - [Commits](Kludex/starlette@0.49.1...1.3.1) --- updated-dependencies: - dependency-name: starlette dependency-version: 1.3.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
There was a problem hiding this comment.
LGTM — lockfile-only Dependabot bump of starlette to 1.3.1. The SDK's own constraint (starlette>=0.27, unbounded) means end users already resolve 1.3.1; this only updates the CI/dev pin, and the full test matrix runs against it. The one inline note is a non-blocking follow-up suggestion, not a merge blocker.
Extended reasoning...
Overview
This PR is a single-file Dependabot change: uv.lock bumps starlette from 0.49.1 to 1.3.1. No source code, no pyproject.toml constraint change. The SDK's runtime requirement remains starlette>=0.27 (>=0.48 on 3.14) with no upper bound, so published-package users already resolve 1.3.1 today — this pin only affects the SDK's own CI/dev environment.
Security risks
The delta is upstream hardening (FormParser max_fields/max_part_size enforcement, StaticFiles absolute-path rejection, FileResponse range clamping) — security-positive rather than risky. The one behavioral interaction found is the inline nit: starlette's new 1 MB form-field limit raises an HTTPException(400) that the OAuth handlers' except ValidationError blocks don't translate into the RFC 6749 JSON error shape. The trigger is adversarial (>1 MB field or >1000 fields), the status code is still a correct 400, and the exposure exists independently of this PR given the unbounded floor — so it's a follow-up, not a blocker.
Level of scrutiny
Lockfile-only dependency bumps warrant moderate scrutiny focused on behavioral deltas in the upgraded package, which the bug hunting system performed: all imported starlette symbols verified present in 1.3.1, removed 1.0 APIs (on_startup/on_event/@app.route/WSGIMiddleware) confirmed unused, sse-starlette 3.0.2 compatibility checked, and the request.url structured-components change confirmed not to touch server-side SDK code. The full CI matrix (3.10–3.14 × ubuntu/windows, 100% coverage gate) runs against the new pin, which is the definitive compatibility check.
Other factors
No human reviewer comments and no prior bot reviews on this PR. The major-version jump (0.49 → 1.3) looks larger than it is — the audited delta shows no removed APIs in use and the repo does not use starlette's multipart/form features outside the OAuth handlers flagged inline. Approving with the inline nit standing as a suggested follow-up.
| name = "starlette" | ||
| version = "0.49.1" | ||
| version = "1.3.1" | ||
| source = { registry = "https://pypi.org/simple" } | ||
| dependencies = [ | ||
| { name = "anyio" }, | ||
| { name = "typing-extensions", marker = "python_full_version < '3.13'" }, | ||
| ] | ||
| sdist = { url = "https://files.pythonhosted.org/packages/1b/3f/507c21db33b66fb027a332f2cb3abbbe924cc3a79ced12f01ed8645955c9/starlette-0.49.1.tar.gz", hash = "sha256:481a43b71e24ed8c43b11ea02f5353d77840e01480881b8cb5a26b8cae64a8cb", size = 2654703, upload-time = "2025-10-28T17:34:10.928Z" } | ||
| sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } | ||
| wheels = [ | ||
| { url = "https://files.pythonhosted.org/packages/51/da/545b75d420bb23b5d494b0517757b351963e974e79933f01e05c929f20a6/starlette-0.49.1-py3-none-any.whl", hash = "sha256:d92ce9f07e4a3caa3ac13a79523bd18e3bc0042bb8ff2d759a8e7dd0e1859875", size = 74175, upload-time = "2025-10-28T17:34:09.13Z" }, | ||
| { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, | ||
| ] |
There was a problem hiding this comment.
🟡 Starlette 1.3.1 newly enforces max_part_size=1MB / max_fields=1000 on application/x-www-form-urlencoded bodies (Kludex/starlette#3329, #3331), raising an HTTPException(400) that the SDK's OAuth handlers (token.py, revoke.py, authorize.py POST, client_auth.py) don't catch — they only catch pydantic ValidationError around request.form(), so oversized posts to /token or /revoke now return a text/plain 400 instead of the RFC 6749 §5.2 JSON error object. Not a merge blocker (pyproject's unbounded starlette>=0.27 floor means users can already resolve 1.3.1, and the trigger is adversarial: a >1MB field or >1000 fields), but a follow-up could catch starlette's HTTPException around request.form() in these handlers and translate it into the OAuth invalid_request JSON shape.
Extended reasoning...
What changed in starlette
In starlette 0.49.1, FormParser (the parser for application/x-www-form-urlencoded bodies) had no size limits — Request._get_form simply ran FormParser(self.headers, self.stream()); await form_parser.parse() with no exception path. Starting in 1.3.1 (Kludex/starlette#3329 and #3331), FormParser enforces max_fields=1000 and max_part_size=1MB per field (verified in the installed wheel, formparsers.py:82-97). Exceeding either limit raises MultiPartException, which Request._get_form converts into a starlette HTTPException(status_code=400, detail='Field exceeded maximum size of 1024KB.' / 'Too many fields...') when app is in scope (requests.py:296-308).
Why the SDK's OAuth handlers are affected
The SDK's OAuth handlers all call await request.form() inside try blocks that catch only pydantic ValidationError (or AuthenticationError further up):
src/mcp/server/auth/handlers/token.py:121src/mcp/server/auth/handlers/revoke.py:50src/mcp/server/auth/handlers/authorize.py:150(POST branch)src/mcp/server/auth/middleware/client_auth.py:54(called by token/revoke handlers)
These handlers deliberately construct RFC 6749 §5.2-conformant JSON error objects (TokenErrorResponse / RevocationErrorResponse via PydanticJSONResponse, with Cache-Control: no-store). The new starlette HTTPException is not caught by any of them, so it escapes to the Starlette app's ExceptionMiddleware and is rendered as a text/plain 400 response. If the handlers are composed as raw ASGI without a Starlette app in scope, the raw MultiPartException propagates and produces a 500 instead.
Step-by-step proof
- A client POSTs to
/tokenwithContent-Type: application/x-www-form-urlencodedand a body containing one field larger than 1 MiB (e.g. a hugeclient_assertionvalue):grant_type=client_credentials&client_assertion=AAAA...(>1MB). TokenHandler.handle(token.py:121) callsawait request.form()insidetry: ... except ValidationError.- In starlette 1.3.1,
FormParser.parse()accumulates the field value, hits the 1 MiBmax_part_sizecheck added in EnforceFormParserlimits in parser callbacks Kludex/starlette#3331, and raisesMultiPartException('Field exceeded maximum size of 1024KB.'). Request._get_formcatches it and re-raises asstarlette.exceptions.HTTPException(400)(because the request is served by a Starlette app).- This is not a
ValidationError, so the handler'sexceptdoesn't fire — the carefully builtTokenErrorResponse(error='invalid_request', ...)JSON path is bypassed. ExceptionMiddlewarecatches theHTTPExceptionand returns400with bodyField exceeded maximum size of 1024KB.astext/plain— violating RFC 6749 §5.2, which requires the token endpoint to return a JSON error object.
Under 0.49.1, the same request parses successfully (unbounded buffering) and reaches the handler's normal validation path — so the changed error shape is introduced by this bump.
Why this is not a merge blocker
pyproject.tomlconstrainsstarlette>=0.27with no upper bound, so end users installing the publishedmcppackage already resolve 1.3.1 today regardless of this PR — the lockfile only pins the SDK's own CI/dev environment. Blocking this bump would shield no one.- The trigger is adversarial or an extreme edge case: no realistic OAuth client sends a single >1 MiB form field or >1000 fields (even large RFC 7523
client_assertionJWTs are orders of magnitude below 1 MiB). The status code is still an RFC-correct 400; only the body shape/content-type differ. - The upstream change is intentional DoS hardening — 0.49.1's unbounded in-memory buffering of urlencoded bodies was strictly worse. Real deployments behind reverse proxies (e.g. nginx's 1 MB default body limit) would often preempt the app with a 413 anyway.
Suggested follow-up fix
In the four handlers listed above, catch starlette's HTTPException (or MultiPartException) around await request.form() and translate it into the OAuth invalid_request JSON error shape, e.g. TokenErrorResponse(error='invalid_request', error_description='request body too large') with status 400. Alternatively, pass explicit max_part_size/max_fields to request.form() and handle the failure there.
For completeness: the rest of the 0.49.1 → 1.3.1 delta was audited against SDK usage and no other affected paths were found — all imported starlette symbols still exist, removed 1.0 APIs (on_startup/on_event/@app.route/WSGIMiddleware) are unused, sse-starlette 3.0.2's imports remain valid, and request.url host-header behavior changes (starlette#3326) don't touch server-side SDK code.
Bumps starlette from 0.49.1 to 1.3.1.
Release notes
Sourced from starlette's releases.
... (truncated)
Changelog
Sourced from starlette's changelog.
... (truncated)
Commits
8ebffd0Version 1.3.1 (#3330)25b8e17EnforceFormParserlimits in parser callbacks (#3331)dba1c4bEnforcemax_fieldsandmax_part_sizeinFormParser(#3329)45e51dcUseStarletteDeprecationWarninginstead ofDeprecationWarning(#3119)5f8610cVersion 1.3.0 (#3327)167b585Buildrequest.urlfrom structured components (#3326)3730925Useremoveprefixto strip weak ETag indicator inis_not_modified(#3193)e6f7ad1avoid collapsing exception groups from user code (#2830)115228fAnnotate URLPath protocol parameter with Literal (#3285)113f193docs: replace inline ASGI server list with link to canonical implemen… (#3204)Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)You can disable automated security fix PRs for this repo from the Security Alerts page.