Skip to content

set_cookie: normalize max_age through int() to prevent cookie attribute injection - #3763

Merged
bdarnell merged 3 commits into
tornadoweb:masterfrom
zhangyan8216:fix/max-age-sanitization
Oct 6, 2026
Merged

bdarnell merged 3 commits into
tornadoweb:masterfrom
zhangyan8216:fix/max-age-sanitization

Conversation

@zhangyan8216

Copy link
Copy Markdown
Contributor

RequestHandler.set_cookie assigns the keyword-only max_age parameter to the cookie Morsel outside the attribute-allowlist validation loops that were added in 6.5.5 (CVE-2026-35536) and extended in 6.5.8 (CVE-2026-91991). A non-int max_age containing ; is therefore written into the Set-Cookie header verbatim, injecting arbitrary cookie attributes (Path, Domain, SameSite, Secure, ...) — a third residual entry point of the same incomplete-fix chain.

self.set_cookie("foo", "bar", max_age="86400; Path=/evil; Secure")
# 6.5.10: Set-Cookie: foo=bar; Max-Age=86400; Path=/evil; Secure; Path=/

As discussed in GHSA-c6v4-qj2h-f748, the fix is to normalize through int(), so a value that is not a valid integer is rejected rather than trusted:

morsel["max-age"] = str(int(max_age))

This is safe for all legitimate callers (ints, bools, and int-valued objects pass through unchanged; floats truncate as before) and consistent with the existing loud-rejection behavior for invalid attribute names/values.

Test included: test_set_cookie_max_age_sanitized verifies that a max_age payload containing cookie attributes is rejected with ValueError and nothing is smuggled into the header.

Refs GHSA-c6v4-qj2h-f748

…te injection

The max_age parameter was assigned to the cookie Morsel outside the
attribute-allowlist validation loops (added for CVE-2026-35536 and
CVE-2026-91991), so a non-int max_age containing ";" would inject
arbitrary cookie attributes into the Set-Cookie header verbatim.
Normalizing through int() rejects such values.

Refs GHSA-c6v4-qj2h-f748
The test deliberately passes a non-int max_age (the injection
payload from GHSA-c6v4-qj2h-f748), which mypy rightly flags
against the int | None signature.
Same injection payload semantics, shorter string.
@bdarnell
bdarnell merged commit 166db51 into tornadoweb:master Oct 6, 2026
17 checks passed
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