CVE-2026-85078
sanic chunked trailer request smuggling allows hidden second request execution
Sanic is an opensource python web server/framework. In version 25.12.0, Sanic's core HTTP/1.1 chunked-body handling does not fully consume the trailer-part after the terminating zero chunk before reusing the keep-alive connection buffer. A remote unauthenticated client can place attacker-controlled bytes in that trailer region, causing Sanic to parse and route them as a hidden second request after the outer request. This breaks HTTP request-boundary integrity and can provide a request-smuggling primitive when Sanic is deployed behind intermediaries. This issue is fixed in version 25.12.1.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- Low
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used
6.5
sanic chunked trailer request smuggling allows hidden second request execution
Sanic is an opensource python web server/framework. In version 25.12.0, Sanic's core HTTP/1.1 chunked-body handling does not fully consume the trailer-part after the terminating zero chunk before reusing the keep-alive connection buffer. A remote unauthenticated client can place attacker-controlled bytes in that trailer region, causing Sanic to parse and route them as a hidden second request after the outer request. This breaks HTTP request-boundary integrity and can provide a request-smuggling primitive when Sanic is deployed behind intermediaries. This issue is fixed in version 25.12.1.
- Record
CVE-2026-85078- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-444
- Updated
- 2026-11-02
EUVDEUVD-2026-81577
6.5
Sanic is an opensource python web server/framework. In version 25.12.0, Sanic's core HTTP/1.1 chunked-body handling does not fully consume the trailer-part after the terminating zero chunk before reusing the keep-alive connection buffer. A remote unauthenticated client can place attacker-controlled bytes in that trailer region, causing Sanic to parse and route them as a hidden second request after the outer request. This breaks HTTP request-boundary integrity and can provide a request-smuggling primitive when Sanic is deployed behind intermediaries. This issue is fixed in version 25.12.1.
- Record
EUVD-2026-81577- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-wmj6-g64g-j7q5
6.5
sanic chunked trailer request smuggling allows hidden second request execution
Description
Sanic's HTTP/1.1 chunked-body handling does not fully consume the trailer-part after the terminating 0\r\n chunk. Because of that, attacker-controlled bytes left in the connection buffer after the first chunked request can be interpreted as the start of a new HTTP request on the same keep-alive connection. In the attached verified proof, a single outer POST / request that correctly returns 405 Method Not Allowed is followed, within the same TCP send, by a hidden second request smuggled through the chunked trailer area. Sanic parses and executes that second request as a real independent request.
The issue is a request-boundary integrity failure in Sanic's core HTTP/1.1 parser. The verified impact is not speculative. The attached proof shows that one TCP payload produces two server responses: first the expected 405 for the outer POST /, then a separate 200 OK for a hidden GET /. A second exploit variant changes the hidden request path and receives a real 404 Not Found, proving that the hidden second request is not a hard-coded artifact but an actually routed backend request. A control case with a two-character trailer field name shifts the leftover bytes from GET to :GET, which changes the second response accordingly and confirms that the root cause is incorrect trailer consumption rather than legitimate pipelining.
Steps To Reproduce
- Start a Sanic HTTP/1.1 service on a keep-alive connection path. In the verified run, the local target listened on
127.0.0.1:9381and the root route allowedGET /but notPOST /. - From the package root, run the provided PoC:
python3 evidence/vuln_001_chunked_trailer_smuggle.py | tee evidence/vuln_001_chunked_trailer_smuggle.run.txt
- Review the baseline case in
evidence/vuln_001_chunked_trailer_smuggle.run.txt. A normal chunked request with0\r\n\r\nreturns exactly one response block:
=== CASE: baseline_no_trailer ===
Received response blocks: 1
HTTP/1.1 405 Method Not Allowed
- Review the exploit case that hides
GET /in the trailer area:
=== CASE: exploit_smuggled_root ===
The PoC sends a single TCP payload containing:
POST / HTTP/1.1
Host: 127.0.0.1:9381
Connection: keep-alive
Transfer-Encoding: chunked
1
X
0
a:GET / HTTP/1.1
Host: 127.0.0.1:9381
- Confirm that Sanic returns two response blocks from that one send:
Received response blocks: 2
HTTP/1.1 405 Method Not Allowed
...
HTTP/1.1 200 OK
...
{"test":true}
- Review the second exploit case that changes the smuggled request path to a nonexistent route:
=== CASE: exploit_smuggled_404 ===
Confirm that Sanic again returns two response blocks and that the second response is a real routed 404 Not Found for the attacker-controlled hidden path.
7. Review the control case with a two-character trailer field name:
=== CASE: offset_control_two_char_field_name ===
Confirm that the second request is now interpreted as :GET /, producing a second 405 with Method :GET not allowed for URL /. This demonstrates that the leftover bytes begin at a parser offset inside the trailer region and are then reinterpreted as a new request line.
Recommendations
After parsing the terminating 0 chunk, Sanic must continue parsing and fully consuming the trailer-part until the final empty line before the connection buffer is reused. If trailer support is not intended, the safer behavior is to reject any request that contains bytes after the terminating 0\r\n other than the expected final empty line, and then close the connection instead of keeping it alive.
Add regression coverage for all three cases shown in the evidence: a normal 0\r\n\r\n chunked termination, a legal trailer that must be fully consumed, and a malicious trailer that must never cause a second backend request to be parsed. The fix needs to guarantee that no bytes from trailer processing can remain in the buffer as a candidate…
- Record
GHSA-wmj6-g64g-j7q5- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-444
- Updated
- 2026-11-02
NVD
6.5
Sanic is an opensource python web server/framework. In version 25.12.0, Sanic's core HTTP/1.1 chunked-body handling does not fully consume the trailer-part after the terminating zero chunk before reusing the keep-alive connection buffer. A remote unauthenticated client can place attacker-controlled bytes in that trailer region, causing Sanic to parse and route them as a hidden second request after the outer request. This breaks HTTP request-boundary integrity and can provide a request-smuggling primitive when Sanic is deployed behind intermediaries. This issue is fixed in version 25.12.1.
- Record
CVE-2026-85078- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-444
- Updated
- 2026-11-02
OSV
6.5
sanic chunked trailer request smuggling allows hidden second request execution
Sanic is an opensource python web server/framework. In version 25.12.0, Sanic's core HTTP/1.1 chunked-body handling does not fully consume the trailer-part after the terminating zero chunk before reusing the keep-alive connection buffer. A remote unauthenticated client can place attacker-controlled bytes in that trailer region, causing Sanic to parse and route them as a hidden second request after the outer request. This breaks HTTP request-boundary integrity and can provide a request-smuggling primitive when Sanic is deployed behind intermediaries. This issue is fixed in version 25.12.1.
- Record
CVE-2026-85078- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-444
- Updated
- 2026-11-02
OSVGHSA-wmj6-g64g-j7q5
6.5
sanic chunked trailer request smuggling allows hidden second request execution
Description
Sanic's HTTP/1.1 chunked-body handling does not fully consume the trailer-part after the terminating 0\r\n chunk. Because of that, attacker-controlled bytes left in the connection buffer after the first chunked request can be interpreted as the start of a new HTTP request on the same keep-alive connection. In the attached verified proof, a single outer POST / request that correctly returns 405 Method Not Allowed is followed, within the same TCP send, by a hidden second request smuggled through the chunked trailer area. Sanic parses and executes that second request as a real independent request.
The issue is a request-boundary integrity failure in Sanic's core HTTP/1.1 parser. The verified impact is not speculative. The attached proof shows that one TCP payload produces two server responses: first the expected 405 for the outer POST /, then a separate 200 OK for a hidden GET /. A second exploit variant changes the hidden request path and receives a real 404 Not Found, proving that the hidden second request is not a hard-coded artifact but an actually routed backend request. A control case with a two-character trailer field name shifts the leftover bytes from GET to :GET, which changes the second response accordingly and confirms that the root cause is incorrect trailer consumption rather than legitimate pipelining.
Steps To Reproduce
- Start a Sanic HTTP/1.1 service on a keep-alive connection path. In the verified run, the local target listened on
127.0.0.1:9381and the root route allowedGET /but notPOST /. - From the package root, run the provided PoC:
python3 evidence/vuln_001_chunked_trailer_smuggle.py | tee evidence/vuln_001_chunked_trailer_smuggle.run.txt
- Review the baseline case in
evidence/vuln_001_chunked_trailer_smuggle.run.txt. A normal chunked request with0\r\n\r\nreturns exactly one response block:
=== CASE: baseline_no_trailer ===
Received response blocks: 1
HTTP/1.1 405 Method Not Allowed
- Review the exploit case that hides
GET /in the trailer area:
=== CASE: exploit_smuggled_root ===
The PoC sends a single TCP payload containing:
POST / HTTP/1.1
Host: 127.0.0.1:9381
Connection: keep-alive
Transfer-Encoding: chunked
1
X
0
a:GET / HTTP/1.1
Host: 127.0.0.1:9381
- Confirm that Sanic returns two response blocks from that one send:
Received response blocks: 2
HTTP/1.1 405 Method Not Allowed
...
HTTP/1.1 200 OK
...
{"test":true}
- Review the second exploit case that changes the smuggled request path to a nonexistent route:
=== CASE: exploit_smuggled_404 ===
Confirm that Sanic again returns two response blocks and that the second response is a real routed 404 Not Found for the attacker-controlled hidden path.
7. Review the control case with a two-character trailer field name:
=== CASE: offset_control_two_char_field_name ===
Confirm that the second request is now interpreted as :GET /, producing a second 405 with Method :GET not allowed for URL /. This demonstrates that the leftover bytes begin at a parser offset inside the trailer region and are then reinterpreted as a new request line.
Recommendations
After parsing the terminating 0 chunk, Sanic must continue parsing and fully consuming the trailer-part until the final empty line before the connection buffer is reused. If trailer support is not intended, the safer behavior is to reject any request that contains bytes after the terminating 0\r\n other than the expected final empty line, and then close the connection instead of keeping it alive.
Add regression coverage for all three cases shown in the evidence: a normal 0\r\n\r\n chunked termination, a legal trailer that must be fully consumed, and a malicious trailer that must never cause a second backend request to be parsed. The fix needs to guarantee that no bytes from trailer processing can remain in the buffer as a candidate…
- Record
GHSA-wmj6-g64g-j7q5- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-444
- Updated
- 2026-11-02
OSVPYSEC-2026-4162
6.5
sanic chunked trailer request smuggling allows hidden second request execution
Description
Sanic's HTTP/1.1 chunked-body handling does not fully consume the trailer-part after the terminating 0\r\n chunk. Because of that, attacker-controlled bytes left in the connection buffer after the first chunked request can be interpreted as the start of a new HTTP request on the same keep-alive connection. In the attached verified proof, a single outer POST / request that correctly returns 405 Method Not Allowed is followed, within the same TCP send, by a hidden second request smuggled through the chunked trailer area. Sanic parses and executes that second request as a real independent request.
The issue is a request-boundary integrity failure in Sanic's core HTTP/1.1 parser. The verified impact is not speculative. The attached proof shows that one TCP payload produces two server responses: first the expected 405 for the outer POST /, then a separate 200 OK for a hidden GET /. A second exploit variant changes the hidden request path and receives a real 404 Not Found, proving that the hidden second request is not a hard-coded artifact but an actually routed backend request. A control case with a two-character trailer field name shifts the leftover bytes from GET to :GET, which changes the second response accordingly and confirms that the root cause is incorrect trailer consumption rather than legitimate pipelining.
Steps To Reproduce
- Start a Sanic HTTP/1.1 service on a keep-alive connection path. In the verified run, the local target listened on
127.0.0.1:9381and the root route allowedGET /but notPOST /. - From the package root, run the provided PoC:
python3 evidence/vuln_001_chunked_trailer_smuggle.py | tee evidence/vuln_001_chunked_trailer_smuggle.run.txt
- Review the baseline case in
evidence/vuln_001_chunked_trailer_smuggle.run.txt. A normal chunked request with0\r\n\r\nreturns exactly one response block:
=== CASE: baseline_no_trailer ===
Received response blocks: 1
HTTP/1.1 405 Method Not Allowed
- Review the exploit case that hides
GET /in the trailer area:
=== CASE: exploit_smuggled_root ===
The PoC sends a single TCP payload containing:
POST / HTTP/1.1
Host: 127.0.0.1:9381
Connection: keep-alive
Transfer-Encoding: chunked
1
X
0
a:GET / HTTP/1.1
Host: 127.0.0.1:9381
- Confirm that Sanic returns two response blocks from that one send:
Received response blocks: 2
HTTP/1.1 405 Method Not Allowed
...
HTTP/1.1 200 OK
...
{"test":true}
- Review the second exploit case that changes the smuggled request path to a nonexistent route:
=== CASE: exploit_smuggled_404 ===
Confirm that Sanic again returns two response blocks and that the second response is a real routed 404 Not Found for the attacker-controlled hidden path.
7. Review the control case with a two-character trailer field name:
=== CASE: offset_control_two_char_field_name ===
Confirm that the second request is now interpreted as :GET /, producing a second 405 with Method :GET not allowed for URL /. This demonstrates that the leftover bytes begin at a parser offset inside the trailer region and are then reinterpreted as a new request line.
Recommendations
After parsing the terminating 0 chunk, Sanic must continue parsing and fully consuming the trailer-part until the final empty line before the connection buffer is reused. If trailer support is not intended, the safer behavior is to reject any request that contains bytes after the terminating 0\r\n other than the expected final empty line, and then close the connection instead of keeping it alive.
Add regression coverage for all three cases shown in the evidence: a normal 0\r\n\r\n chunked termination, a legal trailer that must be fully consumed, and a malicious trailer that must never cause a second backend request to be parsed. The fix needs to guarantee that no bytes from trailer processing can remain in the buffer as a candidate…
- Record
PYSEC-2026-4162- CVSS 3.1
- 6.5 Medium
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
sanicPyPI |
before 24.12.1 | 24.12.1 |
sanicPyPI |
25.12.0 up to 25.12.1 | 25.12.1 |
sanicPyPI |
before 24.12.1, plus listed versions 0.1.00.1.10.1.30.1.40.1.5 Show 45 moreShow less0.1.60.1.70.1.80.1.90.2.00.3.00.3.10.4.00.4.10.5.00.5.10.5.20.5.40.6.00.7.00.8.00.8.10.8.20.8.318.12.019.12.019.12.219.12.319.12.419.12.519.3.119.6.019.6.219.6.319.9.020.12.020.12.120.12.220.12.320.12.420.12.520.12.620.12.720.3.020.6.020.6.120.6.220.6.320.9.020.9.1 |
24.12.1 |
sanicPyPI |
before 24.12.1, 25.12.0 up to 25.12.1, plus listed versions 0.1.00.1.10.1.30.1.40.1.5 Show 45 moreShow less0.1.60.1.70.1.80.1.90.2.00.3.00.3.10.4.00.4.10.5.00.5.10.5.20.5.40.6.00.7.00.8.00.8.10.8.20.8.318.12.019.12.019.12.219.12.319.12.419.12.519.3.119.6.019.6.219.6.319.9.020.12.020.12.120.12.220.12.320.12.420.12.520.12.620.12.720.3.020.6.020.6.120.6.220.6.320.9.020.9.1 |
24.12.125.12.1 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-85078 is a medium vulnerability (CVSS 6.5) in Sanic-org Sanic, published September 17, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.51%, the 43rd percentile. Fixed in sanic 24.12.1.
References
github.com/sanic-org/sanic/security/advisories/GHSA-wmj6-g64g-j7q5
github.com/sanic-org/sanic/pull/3164
github.com/sanic-org/sanic/pull/3165
github.com/sanic-org/sanic/commit/47349d689d65fa1907977ac100e867894aeafb22
github.com/sanic-org/sanic/commit/69a10d3b06babaa9e5f6d1af577364e9e53b6dea
github.com/sanic-org/sanic/commit/a332796506c7c588b6930b02a8886e43eb8ea8d6
Show 7 moreShow less
github.com/sanic-org/sanic/releases/tag/v24.12.1
github.com/sanic-org/sanic/releases/tag/v25.12.1
github.com/advisories/GHSA-wmj6-g64g-j7q5
github.com/CVEProject/cvelistV5/tree/main/cves/2026/85xxx/CVE-2026-85078.json
nvd.nist.gov/vuln/detail/CVE-2026-85078
github.com/sanic-org/sanic
pypi.org/project/sanic
Left out here: 60 versions and the rest of 3 source descriptions. The JSON record has them.