[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1010613: bullseye-pu: package twisted/20.3.0-7+deb11u1



Package: release.debian.org
Severity: normal
Tags: bullseye
User: release.debian.org@packages.debian.org
Usertags: pu
X-Debbugs-Cc: doko@debian.org

[ Reason ]
Catching up on outstanding security issues.
Security team deemed them no-dsa.

[ Impact ]
Outstanding security issues remain unresolved.

[ Tests ]
Twisted has a comprehensive test-suite, the relevant updates come with
tests, and no regressions were noticed.

[ Risks ]
The same patches are carried in Ubuntu, and in Debian LTS * ELTS.
They did need some backporting to older releases, but nothing too risky.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
  [x] the issue is verified as fixed in unstable

[ Changes ]
* CVE-2022-21712: Information disclosure results in leaking of HTTP cookie
  and authorization headers when following cross origin redirects
  - debian/patches/CVE-2022-21712-*.patch: Ensure sensitive HTTP headers are
    removed when forming requests, in src/twisted/web/client.py,
    src/twisted/web/test/test_agent.py and src/twisted/web/iweb.py.
  - Thanks Canonical for backporting the patches.
* CVE-2022-21716: Parsing of SSH version identifier field during an SSH
  handshake can result in a denial of service when excessively large packets
  are received
  - debian/patches/CVE-2022-21716-*.patch: Ensure that length of received
    handshake buffer is checked, prior to processing version string in
    src/twisted/conch/ssh/transport.py and
    src/twisted/conch/test/test_transport.py
  - Thanks Canonical for backporting the patches.
* CVE-2022-24801: Correct several defects in HTTP request parsing that could
  permit HTTP request smuggling: disallow signed Content-Length headers,
  forbid illegal characters in chunked extensions, forbid 0x prefix to chunk
  lengths, and only strip space and horizontal tab from header values.
  - debian/patches/CVE-2022-24801-*.patch
* Patch: remove spurious test for illegal whitespace in xmlns, to allow
  tests to pass, again.
  This was a regression introduced by the patch to expat for CVE-2022-25236.
  The resolution upstream was to just delete the test.

[ Other info ]
(Anything else the release team should know.)
diff -Nru twisted-20.3.0/debian/changelog twisted-20.3.0/debian/changelog
--- twisted-20.3.0/debian/changelog	2021-04-24 12:36:24.000000000 -0400
+++ twisted-20.3.0/debian/changelog	2022-05-05 09:59:26.000000000 -0400
@@ -1,3 +1,30 @@
+twisted (20.3.0-7+deb11u1) bullseye; urgency=medium
+
+  * Team upload.
+  * CVE-2022-21712: Information disclosure results in leaking of HTTP cookie
+    and authorization headers when following cross origin redirects
+    - debian/patches/CVE-2022-21712-*.patch: Ensure sensitive HTTP headers are
+      removed when forming requests, in src/twisted/web/client.py,
+      src/twisted/web/test/test_agent.py and src/twisted/web/iweb.py.
+    - Thanks Canonical for backporting the patches.
+  * CVE-2022-21716: Parsing of SSH version identifier field during an SSH
+    handshake can result in a denial of service when excessively large packets
+    are received
+    - debian/patches/CVE-2022-21716-*.patch: Ensure that length of received
+      handshake buffer is checked, prior to processing version string in
+      src/twisted/conch/ssh/transport.py and
+      src/twisted/conch/test/test_transport.py
+    - Thanks Canonical for backporting the patches.
+  * CVE-2022-24801: Correct several defects in HTTP request parsing that could
+    permit HTTP request smuggling: disallow signed Content-Length headers,
+    forbid illegal characters in chunked extensions, forbid 0x prefix to chunk
+    lengths, and only strip space and horizontal tab from header values.
+    - debian/patches/CVE-2022-24801-*.patch
+  * Patch: remove spurious test for illegal whitespace in xmlns, to allow
+    tests to pass, again.
+
+ -- Stefano Rivera <stefanor@debian.org>  Thu, 05 May 2022 09:59:26 -0400
+
 twisted (20.3.0-7) unstable; urgency=medium
 
   * Team upload.
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-10.patch twisted-20.3.0/debian/patches/CVE-2022-21712-10.patch
--- twisted-20.3.0/debian/patches/CVE-2022-21712-10.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-21712-10.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,29 @@
+From 0c44b4806a27d258baf13d6f714f06eddb28da5a Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 15:31:51 -0800
+Subject: [PATCH] correct docstring to suggest the right order
+
+---
+ src/twisted/web/iweb.py | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+--- a/src/twisted/web/iweb.py
++++ b/src/twisted/web/iweb.py
+@@ -716,12 +716,12 @@ class IAgent(Interface):
+     obtained by combining a number of (hypothetical) implementations::
+ 
+         baseAgent = Agent(reactor)
+-        redirect = BrowserLikeRedirectAgent(baseAgent, limit=10)
++        decode = ContentDecoderAgent(baseAgent, [(b"gzip", GzipDecoder())])
++        cookie = CookieAgent(decode, diskStore.cookie)
+         authenticate = AuthenticateAgent(
+-            redirect, [diskStore.credentials, GtkAuthInterface()])
+-        cookie = CookieAgent(authenticate, diskStore.cookie)
+-        decode = ContentDecoderAgent(cookie, [(b"gzip", GzipDecoder())])
+-        cache = CacheAgent(decode, diskStore.cache)
++            cookie, [diskStore.credentials, GtkAuthInterface()])
++        cache = CacheAgent(authenticate, diskStore.cache)
++        redirect = BrowserLikeRedirectAgent(cache, limit=10)
+ 
+         doSomeRequests(cache)
+     """
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-1.patch twisted-20.3.0/debian/patches/CVE-2022-21712-1.patch
--- twisted-20.3.0/debian/patches/CVE-2022-21712-1.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-21712-1.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,273 @@
+From eda4f1e2ec9988a142de244f1a2b285939718c03 Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 12:57:49 -0800
+Subject: [PATCH] failing test for header data leak
+
+---
+ src/twisted/web/test/test_agent.py | 154 +++++++++++++++++++++++------
+ 1 file changed, 123 insertions(+), 31 deletions(-)
+
+--- a/src/twisted/web/test/test_agent.py
++++ b/src/twisted/web/test/test_agent.py
+@@ -5,11 +5,17 @@
+ Tests for L{twisted.web.client.Agent} and related new client APIs.
+ """
+ 
+-import zlib
+-
+ from io import BytesIO
+-
+-from zope.interface.verify import verifyObject
++from twisted.test.iosim import FakeTransport, IOPump
++from twisted.test.proto_helpers import (
++    AccumulatingProtocol,
++    EventLoggingObserver,
++    MemoryReactorClock,
++    StringTransport,
++)
++from twisted.test.test_sslverify import certificatesForAuthorityAndServer
++from typing import Optional, TYPE_CHECKING
++from unittest import skipIf, SkipTest
+ 
+ from twisted.trial.unittest import TestCase, SynchronousTestCase
+ from twisted.web import client, error, http_headers
+@@ -20,8 +26,6 @@ from twisted.internet import defer, task
+ from twisted.python.failure import Failure
+ from twisted.python.compat import cookielib, intToBytes, unicode
+ from twisted.python.components import proxyForInterface
+-from twisted.test.proto_helpers import (StringTransport, MemoryReactorClock,
+-                                        EventLoggingObserver)
+ from twisted.internet.task import Clock
+ from twisted.internet.error import ConnectionRefusedError, ConnectionDone
+ from twisted.internet.error import ConnectionLost
+@@ -30,33 +34,48 @@ from twisted.internet.defer import Defer
+ from twisted.internet.endpoints import TCP4ClientEndpoint
+ from twisted.internet.address import IPv4Address, IPv6Address
+ 
+-from twisted.web.client import (FileBodyProducer, Request, HTTPConnectionPool,
+-                                ResponseDone, _HTTP11ClientFactory, URI)
++from twisted.web.client import (
++    FileBodyProducer,
++    BrowserLikePolicyForHTTPS,
++    Request,
++    HTTPConnectionPool,
++    HostnameCachingHTTPSPolicy,
++    ResponseDone,
++    _HTTP11ClientFactory,
++    URI,
++)
+ 
+ from twisted.web.iweb import (
+-    UNKNOWN_LENGTH, IAgent, IBodyProducer, IResponse, IAgentEndpointFactory,
+-    )
++    IAgent,
++    IBodyProducer,
++    IPolicyForHTTPS,
++    IRequest,
++    IResponse,
++    IAgentEndpointFactory,
++    UNKNOWN_LENGTH,
++)
+ from twisted.web.http_headers import Headers
+ from twisted.web._newclient import HTTP11ClientProtocol, Response
+ 
+ from twisted.internet.interfaces import IOpenSSLClientConnectionCreator
+-from zope.interface.declarations import implementer
+ from twisted.web.iweb import IPolicyForHTTPS
+ from twisted.python.deprecate import getDeprecationWarningString
+ from incremental import Version
++
++import zlib
+ from twisted.web.client import (BrowserLikePolicyForHTTPS,
+                                 HostnameCachingHTTPSPolicy)
+ from twisted.internet.test.test_endpoints import deterministicResolvingReactor
+ from twisted.internet.endpoints import HostnameEndpoint
+-from twisted.test.proto_helpers import AccumulatingProtocol
+-from twisted.test.iosim import IOPump, FakeTransport
+-from twisted.test.test_sslverify import certificatesForAuthorityAndServer
+ from twisted.web.test.injectionhelpers import (
+     MethodInjectionTestsMixin,
+     URIInjectionTestsMixin,
+ )
+ from twisted.web.error import SchemeNotSupported
+ from twisted.logger import globalLogPublisher
++from zope.interface.declarations import implementer
++from zope.interface.verify import verifyObject
++
+ 
+ try:
+     from twisted.internet import ssl
+@@ -2627,12 +2646,30 @@ class ProxyAgentTests(TestCase, FakeReac
+         self.assertEqual(agent._pool.connected, True)
+ 
+ 
++SENSITIVE_HEADERS = [
++    b"authorization",
++    b"cookie",
++    b"cookie2",
++    b"proxy-authorization",
++    b"www-authenticate",
++]
+ 
+-class _RedirectAgentTestsMixin(object):
++if TYPE_CHECKING:
++    testMixinClass = TestCase
++else:
++    testMixinClass = object
++
++
++class _RedirectAgentTestsMixin(testMixinClass):
+     """
+     Test cases mixin for L{RedirectAgentTests} and
+     L{BrowserLikeRedirectAgentTests}.
+     """
++
++    agent: IAgent
++    reactor: MemoryReactorClock
++    protocol: StubHTTPProtocol
++
+     def test_noRedirect(self):
+         """
+         L{client.RedirectAgent} behaves like L{client.Agent} if the response
+@@ -2651,34 +2688,53 @@ class _RedirectAgentTestsMixin(object):
+         self.assertIdentical(response, result)
+         self.assertIdentical(result.previousResponse, None)
+ 
++    def _testRedirectDefault(
++        self,
++        code: int,
++        crossScheme: bool = False,
++        crossDomain: bool = False,
++        requestHeaders: Optional[Headers] = None,
++    ) -> IRequest:
+ 
+-    def _testRedirectDefault(self, code):
+         """
+         When getting a redirect, L{client.RedirectAgent} follows the URL
+         specified in the L{Location} header field and make a new request.
+ 
+         @param code: HTTP status code.
+         """
+-        self.agent.request(b'GET', b'http://example.com/foo')
++        startDomain = b"example.com"
++        startScheme = b"https" if ssl is not None else b"http"
++        startPort = 80 if startScheme == b"http" else 443
++        self.agent.request(
++            b"GET", startScheme + b"://" + startDomain + b"/foo", headers=requestHeaders
++        )
+ 
+         host, port = self.reactor.tcpClients.pop()[:2]
+         self.assertEqual(EXAMPLE_COM_IP, host)
+-        self.assertEqual(80, port)
++        self.assertEqual(startPort, port)
+ 
+         req, res = self.protocol.requests.pop()
+ 
+-        # If possible (i.e.: SSL support is present), run the test with a
++        # If possible (i.e.: TLS support is present), run the test with a
+         # cross-scheme redirect to verify that the scheme is honored; if not,
+         # let's just make sure it works at all.
+-        if ssl is None:
+-            scheme = b'http'
+-            expectedPort = 80
+-        else:
+-            scheme = b'https'
+-            expectedPort = 443
+ 
++        targetScheme = startScheme
++        targetDomain = startDomain
++        targetPort = startPort
++
++        if crossDomain:
++            if ssl is None:
++                raise SkipTest(
++                    "Cross-scheme redirects can't be tested without TLS support."
++                )
++            targetScheme = b"https" if startScheme == b"http" else b"https"
++            targetPort = 443 if startPort == 80 else 443
++
++        targetDomain = b"example.net" if crossDomain else startDomain
+         headers = http_headers.Headers(
+-            {b'location': [scheme + b'://example.com/bar']})
++            {b"location": [targetScheme + b"://" + targetDomain + b"/bar"]}
++        )
+         response = Response((b'HTTP', 1, 1), code, b'OK', headers, None)
+         res.callback(response)
+ 
+@@ -2687,8 +2743,9 @@ class _RedirectAgentTestsMixin(object):
+         self.assertEqual(b'/bar', req2.uri)
+ 
+         host, port = self.reactor.tcpClients.pop()[:2]
+-        self.assertEqual(EXAMPLE_COM_IP, host)
+-        self.assertEqual(expectedPort, port)
++        self.assertEqual(EXAMPLE_NET_IP if crossDomain else EXAMPLE_COM_IP, host)
++        self.assertEqual(targetPort, port)
++        return req2
+ 
+ 
+     def test_redirect301(self):
+@@ -2698,6 +2755,15 @@ class _RedirectAgentTestsMixin(object):
+         self._testRedirectDefault(301)
+ 
+ 
++    def test_redirect301Scheme(self):
++        """
++        L{client.RedirectAgent} follows cross-scheme redirects.
++        """
++        self._testRedirectDefault(
++            301,
++            crossScheme=True,
++        )
++
+     def test_redirect302(self):
+         """
+         L{client.RedirectAgent} follows redirects on status code 302.
+@@ -2712,6 +2778,48 @@ class _RedirectAgentTestsMixin(object):
+         self._testRedirectDefault(307)
+ 
+ 
++    def test_headerSecurity(self):
++        """
++        L{client.RedirectAgent} scrubs sensitive headers when redirecting.
++        """
++        sensitiveHeaderValues = {
++            b"authorization": [b"sensitive-authnz"],
++            b"cookie": [b"sensitive-cookie-data"],
++            b"cookie2": [b"sensitive-cookie2-data"],
++            b"proxy-authorization": [b"sensitive-proxy-auth"],
++            b"wWw-auThentiCate": [b"sensitive-authn"],
++        }
++        otherHeaderValues = {b"x-random-header": [b"x-random-value"]}
++        allHeaders = Headers({**sensitiveHeaderValues, **otherHeaderValues})
++        redirected = self._testRedirectDefault(301, requestHeaders=allHeaders)
++
++        def normHeaders(headers: Headers) -> dict:
++            return {k.lower(): v for (k, v) in headers.getAllRawHeaders()}
++
++        sameOriginHeaders = normHeaders(redirected.headers)
++        self.assertEquals(
++            sameOriginHeaders,
++            {
++                b"host": [b"example.com"],
++                **normHeaders(allHeaders),
++            },
++        )
++
++        redirectedElsewhere = self._testRedirectDefault(
++            301,
++            crossDomain=True,
++            requestHeaders=Headers({**sensitiveHeaderValues, **otherHeaderValues}),
++        )
++        otherOriginHeaders = normHeaders(redirectedElsewhere.headers)
++        self.assertEquals(
++            otherOriginHeaders,
++            {
++                b"host": [b"example.com"],
++                **normHeaders(Headers(otherHeaderValues)),
++            },
++        )
++
++
+     def _testRedirectToGet(self, code, method):
+         """
+         L{client.RedirectAgent} changes the method to I{GET} when getting
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-2.patch twisted-20.3.0/debian/patches/CVE-2022-21712-2.patch
--- twisted-20.3.0/debian/patches/CVE-2022-21712-2.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-21712-2.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,20 @@
+From ecc2ae81c831e58cf1725dfe2e5b6d2951c884a0 Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 13:00:02 -0800
+Subject: [PATCH] assert on correct host
+
+---
+ src/twisted/web/test/test_agent.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/src/twisted/web/test/test_agent.py
++++ b/src/twisted/web/test/test_agent.py
+@@ -2814,7 +2814,7 @@ class _RedirectAgentTestsMixin(testMixin
+         self.assertEquals(
+             otherOriginHeaders,
+             {
+-                b"host": [b"example.com"],
++                b"host": [b"example.net"],
+                 **normHeaders(Headers(otherHeaderValues)),
+             },
+         )
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-3.patch twisted-20.3.0/debian/patches/CVE-2022-21712-3.patch
--- twisted-20.3.0/debian/patches/CVE-2022-21712-3.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-21712-3.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,240 @@
+From c1923d24b6a2752ea5d5686851427e0ec5757543 Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 14:04:27 -0800
+Subject: [PATCH] tests for domain/port/scheme and fix
+
+---
+ src/twisted/web/client.py          | 61 +++++++++++++++++++++---------
+ src/twisted/web/test/test_agent.py | 52 ++++++++++++++++++++-----
+ 2 files changed, 86 insertions(+), 27 deletions(-)
+
+--- a/src/twisted/web/client.py
++++ b/src/twisted/web/client.py
+@@ -14,20 +14,17 @@ import warnings
+ 
+ from urllib.parse import urljoin, urldefrag
+ from urllib.parse import urlunparse as _urlunparse
++from typing import Iterable
+ 
+-import zlib
+ from functools import wraps
+ 
+-from zope.interface import implementer
+-
++import zlib
+ from twisted.python.compat import _PY3, networkString
+ from twisted.python.compat import nativeString, intToBytes, unicode, itervalues
+-from twisted.python.deprecate import deprecatedModuleAttribute, deprecated
++from twisted.python.deprecate import deprecated, deprecatedModuleAttribute, getDeprecationWarningString
+ from twisted.python.failure import Failure
+ from incremental import Version
+ 
+-from twisted.web.iweb import IPolicyForHTTPS, IAgentEndpointFactory
+-from twisted.python.deprecate import getDeprecationWarningString
+ from twisted.web import http
+ from twisted.internet import defer, protocol, task, reactor
+ from twisted.internet.abstract import isIPv6Address
+@@ -36,11 +33,12 @@ from twisted.internet.endpoints import H
+ from twisted.python.util import InsensitiveDict
+ from twisted.python.components import proxyForInterface
+ from twisted.web import error
+-from twisted.web.iweb import UNKNOWN_LENGTH, IAgent, IBodyProducer, IResponse
+ from twisted.web.http_headers import Headers
+ from twisted.logger import Logger
+ 
+ from twisted.web._newclient import _ensureValidURI, _ensureValidMethod
++from twisted.web.iweb import IAgent, IAgentEndpointFactory, IBodyProducer, IPolicyForHTTPS, IResponse, UNKNOWN_LENGTH
++from zope.interface import implementer
+ 
+ 
+ 
+@@ -2089,6 +2087,15 @@ class ContentDecoderAgent(object):
+ 
+ 
+ 
++_canonicalHeaderName = Headers()._canonicalNameCaps
++_defaultSensitiveHeaders = frozenset([
++    b"Authorization",
++    b"Cookie",
++    b"Cookie2",
++    b"Proxy-Authorization",
++    b"WWW-Authenticate",
++])
++
+ @implementer(IAgent)
+ class RedirectAgent(object):
+     """
+@@ -2103,6 +2110,11 @@ class RedirectAgent(object):
+     @param redirectLimit: The maximum number of times the agent is allowed to
+         follow redirects before failing with a L{error.InfiniteRedirection}.
+ 
++    @param sensitiveHeaderNames: An iterable of C{bytes} enumerating the names
++        of headers that must not be transmitted when redirecting to a different
++        origins.  These will be consulted in addition to the protocol-specified
++        set of headers that contain sensitive information.
++
+     @cvar _redirectResponses: A L{list} of HTTP status codes to be redirected
+         for I{GET} and I{HEAD} methods.
+ 
+@@ -2117,9 +2129,17 @@ class RedirectAgent(object):
+     _seeOtherResponses = [http.SEE_OTHER]
+ 
+ 
+-    def __init__(self, agent, redirectLimit=20):
++    def __init__(
++        self,
++        agent: IAgent,
++        redirectLimit: int = 20,
++        sensitiveHeaderNames: Iterable[bytes] = (),
++    ):
+         self._agent = agent
+         self._redirectLimit = redirectLimit
++        sensitive = set(_canonicalHeaderName(each) for each in sensitiveHeaderNames)
++        sensitive.update(_defaultSensitiveHeaders)
++        self._sensitiveHeaderNames = sensitive
+ 
+ 
+     def request(self, method, uri, headers=None, bodyProducer=None):
+@@ -2166,6 +2186,22 @@ class RedirectAgent(object):
+                 response.code, b'No location header field', uri)
+             raise ResponseFailed([Failure(err)], response)
+         location = self._resolveLocation(uri, locationHeaders[0])
++        if headers:
++            parsedURI = URI.fromBytes(uri)
++            parsedLocation = URI.fromBytes(location)
++            sameOrigin = (
++                (parsedURI.scheme == parsedLocation.scheme)
++                and (parsedURI.host == parsedLocation.host)
++                and (parsedURI.port == parsedLocation.port)
++            )
++            if not sameOrigin:
++                headers = Headers(
++                    {
++                        rawName: rawValue
++                        for rawName, rawValue in headers.getAllRawHeaders()
++                        if rawName not in self._sensitiveHeaderNames
++                    }
++                )
+         deferred = self._agent.request(method, location, headers)
+         def _chainResponse(newResponse):
+             newResponse.setPreviousResponse(response)
+--- a/src/twisted/web/test/test_agent.py
++++ b/src/twisted/web/test/test_agent.py
+@@ -2693,6 +2693,7 @@ class _RedirectAgentTestsMixin(testMixin
+         code: int,
+         crossScheme: bool = False,
+         crossDomain: bool = False,
++        crossPort: bool = False,
+         requestHeaders: Optional[Headers] = None,
+     ) -> IRequest:
+ 
+@@ -2723,17 +2724,22 @@ class _RedirectAgentTestsMixin(testMixin
+         targetDomain = startDomain
+         targetPort = startPort
+ 
+-        if crossDomain:
++        if crossScheme:
+             if ssl is None:
+                 raise SkipTest(
+                     "Cross-scheme redirects can't be tested without TLS support."
+                 )
+-            targetScheme = b"https" if startScheme == b"http" else b"https"
+-            targetPort = 443 if startPort == 80 else 443
++            targetScheme = b"https" if startScheme == b"http" else b"http"
++            targetPort = 443 if startPort == 80 else 80
+ 
++        portSyntax = b''
++        if crossPort:
++            targetPort = 8443
++            portSyntax = b':8443'
+         targetDomain = b"example.net" if crossDomain else startDomain
++        locationValue = targetScheme + b"://" + targetDomain + portSyntax + b"/bar"
+         headers = http_headers.Headers(
+-            {b"location": [targetScheme + b"://" + targetDomain + b"/bar"]}
++            {b"location": [locationValue]}
+         )
+         response = Response((b'HTTP', 1, 1), code, b'OK', headers, None)
+         res.callback(response)
+@@ -2778,9 +2784,10 @@ class _RedirectAgentTestsMixin(testMixin
+         self._testRedirectDefault(307)
+ 
+ 
+-    def test_headerSecurity(self):
++    def _sensitiveHeadersTest(self, expectedHostHeader: bytes = b"example.com", **crossKwargs: dict) -> None:
+         """
+-        L{client.RedirectAgent} scrubs sensitive headers when redirecting.
++        L{client.RedirectAgent} scrubs sensitive headers when redirecting
++        between differing origins.
+         """
+         sensitiveHeaderValues = {
+             b"authorization": [b"sensitive-authnz"],
+@@ -2788,6 +2795,7 @@ class _RedirectAgentTestsMixin(testMixin
+             b"cookie2": [b"sensitive-cookie2-data"],
+             b"proxy-authorization": [b"sensitive-proxy-auth"],
+             b"wWw-auThentiCate": [b"sensitive-authn"],
++            b"x-custom-sensitive": [b"sensitive-custom"],
+         }
+         otherHeaderValues = {b"x-random-header": [b"x-random-value"]}
+         allHeaders = Headers({**sensitiveHeaderValues, **otherHeaderValues})
+@@ -2807,18 +2815,38 @@ class _RedirectAgentTestsMixin(testMixin
+ 
+         redirectedElsewhere = self._testRedirectDefault(
+             301,
+-            crossDomain=True,
++            **crossKwargs,
+             requestHeaders=Headers({**sensitiveHeaderValues, **otherHeaderValues}),
+         )
+         otherOriginHeaders = normHeaders(redirectedElsewhere.headers)
+         self.assertEquals(
+             otherOriginHeaders,
+             {
+-                b"host": [b"example.net"],
++                b"host": [expectedHostHeader],
+                 **normHeaders(Headers(otherHeaderValues)),
+             },
+         )
+ 
++    def test_crossDomainHeaders(self) -> None:
++        """
++        L{client.RedirectAgent} scrubs sensitive headers when redirecting
++        between differing domains.
++        """
++        self._sensitiveHeadersTest(crossDomain=True, expectedHostHeader=b'example.net')
++
++    def test_crossPortHeaders(self) -> None:
++        """
++        L{client.RedirectAgent} scrubs sensitive headers when redirecting
++        between differing ports.
++        """
++        self._sensitiveHeadersTest(crossPort=True, expectedHostHeader=b'example.com:8443')
++
++    def test_crossSchemeHeaders(self) -> None:
++        """
++        L{client.RedirectAgent} scrubs sensitive headers when redirecting
++        between differing schemes.
++        """
++        self._sensitiveHeadersTest(crossScheme=True)
+ 
+     def _testRedirectToGet(self, code, method):
+         """
+@@ -3045,7 +3073,9 @@ class RedirectAgentTests(TestCase, FakeR
+         @return: a new L{twisted.web.client.RedirectAgent}
+         """
+         return client.RedirectAgent(
+-            self.buildAgentForWrapperTest(self.reactor))
++            self.buildAgentForWrapperTest(self.reactor),
++            sensitiveHeaderNames=[b"X-Custom-sensitive"],
++        )
+ 
+ 
+     def setUp(self):
+@@ -3084,7 +3114,8 @@ class BrowserLikeRedirectAgentTests(Test
+         @return: a new L{twisted.web.client.BrowserLikeRedirectAgent}
+         """
+         return client.BrowserLikeRedirectAgent(
+-            self.buildAgentForWrapperTest(self.reactor))
++            self.buildAgentForWrapperTest(self.reactor),
++            sensitiveHeaderNames=[b"x-Custom-sensitive"])
+ 
+ 
+     def setUp(self):
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-4.patch twisted-20.3.0/debian/patches/CVE-2022-21712-4.patch
--- twisted-20.3.0/debian/patches/CVE-2022-21712-4.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-21712-4.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,15 @@
+From 562dcff9c7dd846857932443a0a2935f74c2a2de Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 14:11:46 -0800
+Subject: [PATCH] topfile
+
+---
+ src/twisted/topfiles/10294.bugfix | 1 +
+ 1 file changed, 1 insertion(+)
+ create mode 100644 src/twisted/topfiles/10294.bugfix
+
+--- /dev/null
++++ b/src/twisted/topfiles/10294.bugfix
+@@ -0,0 +1 @@
++twisted.web.client.RedirectAgent and twisted.web.client.BrowserLikeRedirectAgent now properly remove sensitive headers when redirecting to a different origin.
+\ No newline at end of file
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-5.patch twisted-20.3.0/debian/patches/CVE-2022-21712-5.patch
--- twisted-20.3.0/debian/patches/CVE-2022-21712-5.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-21712-5.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,222 @@
+From 4ae6732a30893178891ee3a383d8a737e0ec21eb Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 14:16:18 -0800
+Subject: [PATCH] reblackening
+
+---
+ src/twisted/web/client.py          | 41 +++++++++++++++--------
+ src/twisted/web/test/test_agent.py | 52 ++++++++++++++++--------------
+ 2 files changed, 55 insertions(+), 38 deletions(-)
+
+--- a/src/twisted/web/client.py
++++ b/src/twisted/web/client.py
+@@ -11,17 +11,23 @@ from __future__ import division, absolut
+ import os
+ import collections
+ import warnings
++import zlib
+ 
+-from urllib.parse import urljoin, urldefrag
+-from urllib.parse import urlunparse as _urlunparse
+ from typing import Iterable
++from urllib.parse import urldefrag, urljoin, urlunparse as _urlunparse
++
++from zope.interface import implementer
+ 
+ from functools import wraps
+ 
+-import zlib
+ from twisted.python.compat import _PY3, networkString
+ from twisted.python.compat import nativeString, intToBytes, unicode, itervalues
+-from twisted.python.deprecate import deprecated, deprecatedModuleAttribute, getDeprecationWarningString
++from twisted.python.deprecate import (
++    deprecated,
++    deprecatedModuleAttribute,
++    getDeprecationWarningString,
++)
++
+ from twisted.python.failure import Failure
+ from incremental import Version
+ 
+@@ -37,9 +43,14 @@ from twisted.web.http_headers import Hea
+ from twisted.logger import Logger
+ 
+ from twisted.web._newclient import _ensureValidURI, _ensureValidMethod
+-from twisted.web.iweb import IAgent, IAgentEndpointFactory, IBodyProducer, IPolicyForHTTPS, IResponse, UNKNOWN_LENGTH
+-from zope.interface import implementer
+-
++from twisted.web.iweb import (
++    UNKNOWN_LENGTH,
++    IAgent,
++    IAgentEndpointFactory,
++    IBodyProducer,
++    IPolicyForHTTPS,
++    IResponse,
++)
+ 
+ 
+ def urlunparse(parts):
+@@ -2088,13 +2099,16 @@ class ContentDecoderAgent(object):
+ 
+ 
+ _canonicalHeaderName = Headers()._canonicalNameCaps
+-_defaultSensitiveHeaders = frozenset([
+-    b"Authorization",
+-    b"Cookie",
+-    b"Cookie2",
+-    b"Proxy-Authorization",
+-    b"WWW-Authenticate",
+-])
++_defaultSensitiveHeaders = frozenset(
++    [
++        b"Authorization",
++        b"Cookie",
++        b"Cookie2",
++        b"Proxy-Authorization",
++        b"WWW-Authenticate",
++    ]
++)
++
+ 
+ @implementer(IAgent)
+ class RedirectAgent(object):
+@@ -2137,7 +2151,7 @@ class RedirectAgent(object):
+     ):
+         self._agent = agent
+         self._redirectLimit = redirectLimit
+-        sensitive = set(_canonicalHeaderName(each) for each in sensitiveHeaderNames)
++        sensitive = {_canonicalHeaderName(each) for each in sensitiveHeaderNames}
+         sensitive.update(_defaultSensitiveHeaders)
+         self._sensitiveHeaderNames = sensitive
+ 
+--- a/src/twisted/web/test/test_agent.py
++++ b/src/twisted/web/test/test_agent.py
+@@ -4,18 +4,13 @@
+ """
+ Tests for L{twisted.web.client.Agent} and related new client APIs.
+ """
+-
++import zlib
+ from io import BytesIO
+-from twisted.test.iosim import FakeTransport, IOPump
+-from twisted.test.proto_helpers import (
+-    AccumulatingProtocol,
+-    EventLoggingObserver,
+-    MemoryReactorClock,
+-    StringTransport,
+-)
+-from twisted.test.test_sslverify import certificatesForAuthorityAndServer
+-from typing import Optional, TYPE_CHECKING
+-from unittest import skipIf, SkipTest
++from typing import TYPE_CHECKING, Optional
++from unittest import SkipTest, skipIf
++
++from zope.interface.declarations import implementer
++from zope.interface.verify import verifyObject
+ 
+ from twisted.trial.unittest import TestCase, SynchronousTestCase
+ from twisted.web import client, error, http_headers
+@@ -34,25 +29,34 @@ from twisted.internet.defer import Defer
+ from twisted.internet.endpoints import TCP4ClientEndpoint
+ from twisted.internet.address import IPv4Address, IPv6Address
+ 
++from twisted.test.iosim import FakeTransport, IOPump
++from twisted.test.proto_helpers import (
++    AccumulatingProtocol,
++    EventLoggingObserver,
++    MemoryReactorClock,
++    StringTransport,
++)
++from twisted.test.test_sslverify import certificatesForAuthorityAndServer
++
+ from twisted.web.client import (
++    URI,
+     FileBodyProducer,
+     BrowserLikePolicyForHTTPS,
+     Request,
+-    HTTPConnectionPool,
+     HostnameCachingHTTPSPolicy,
++    HTTPConnectionPool,
+     ResponseDone,
+     _HTTP11ClientFactory,
+-    URI,
+ )
+ 
+ from twisted.web.iweb import (
++    UNKNOWN_LENGTH,
+     IAgent,
+     IBodyProducer,
+     IPolicyForHTTPS,
+     IRequest,
+     IResponse,
+     IAgentEndpointFactory,
+-    UNKNOWN_LENGTH,
+ )
+ from twisted.web.http_headers import Headers
+ from twisted.web._newclient import HTTP11ClientProtocol, Response
+@@ -62,7 +66,6 @@ from twisted.web.iweb import IPolicyForH
+ from twisted.python.deprecate import getDeprecationWarningString
+ from incremental import Version
+ 
+-import zlib
+ from twisted.web.client import (BrowserLikePolicyForHTTPS,
+                                 HostnameCachingHTTPSPolicy)
+ from twisted.internet.test.test_endpoints import deterministicResolvingReactor
+@@ -73,8 +76,6 @@ from twisted.web.test.injectionhelpers i
+ )
+ from twisted.web.error import SchemeNotSupported
+ from twisted.logger import globalLogPublisher
+-from zope.interface.declarations import implementer
+-from zope.interface.verify import verifyObject
+ 
+ 
+ try:
+@@ -2732,15 +2733,13 @@ class _RedirectAgentTestsMixin(testMixin
+             targetScheme = b"https" if startScheme == b"http" else b"http"
+             targetPort = 443 if startPort == 80 else 80
+ 
+-        portSyntax = b''
++        portSyntax = b""
+         if crossPort:
+             targetPort = 8443
+-            portSyntax = b':8443'
++            portSyntax = b":8443"
+         targetDomain = b"example.net" if crossDomain else startDomain
+         locationValue = targetScheme + b"://" + targetDomain + portSyntax + b"/bar"
+-        headers = http_headers.Headers(
+-            {b"location": [locationValue]}
+-        )
++        headers = http_headers.Headers({b"location": [locationValue]})
+         response = Response((b'HTTP', 1, 1), code, b'OK', headers, None)
+         res.callback(response)
+ 
+@@ -2784,7 +2783,9 @@ class _RedirectAgentTestsMixin(testMixin
+         self._testRedirectDefault(307)
+ 
+ 
+-    def _sensitiveHeadersTest(self, expectedHostHeader: bytes = b"example.com", **crossKwargs: dict) -> None:
++    def _sensitiveHeadersTest(
++        self, expectedHostHeader: bytes = b"example.com", **crossKwargs: dict
++    ) -> None:
+         """
+         L{client.RedirectAgent} scrubs sensitive headers when redirecting
+         between differing origins.
+@@ -2832,14 +2833,16 @@ class _RedirectAgentTestsMixin(testMixin
+         L{client.RedirectAgent} scrubs sensitive headers when redirecting
+         between differing domains.
+         """
+-        self._sensitiveHeadersTest(crossDomain=True, expectedHostHeader=b'example.net')
++        self._sensitiveHeadersTest(crossDomain=True, expectedHostHeader=b"example.net")
+ 
+     def test_crossPortHeaders(self) -> None:
+         """
+         L{client.RedirectAgent} scrubs sensitive headers when redirecting
+         between differing ports.
+         """
+-        self._sensitiveHeadersTest(crossPort=True, expectedHostHeader=b'example.com:8443')
++        self._sensitiveHeadersTest(
++            crossPort=True, expectedHostHeader=b"example.com:8443"
++        )
+ 
+     def test_crossSchemeHeaders(self) -> None:
+         """
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-6.patch twisted-20.3.0/debian/patches/CVE-2022-21712-6.patch
--- twisted-20.3.0/debian/patches/CVE-2022-21712-6.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-21712-6.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,20 @@
+From 8a4b1f3e0e7d2316944064960d9dcb4556525845 Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 14:16:48 -0800
+Subject: [PATCH] oops
+
+---
+ src/twisted/{topfiles => newsfragments}/10294.bugfix | 0
+ 1 file changed, 0 insertions(+), 0 deletions(-)
+ rename src/twisted/{topfiles => newsfragments}/10294.bugfix (100%)
+
+--- /dev/null
++++ b/src/twisted/newsfragments/10294.bugfix
+@@ -0,0 +1 @@
++twisted.web.client.RedirectAgent and twisted.web.client.BrowserLikeRedirectAgent now properly remove sensitive headers when redirecting to a different origin.
+\ No newline at end of file
+--- a/src/twisted/topfiles/10294.bugfix
++++ /dev/null
+@@ -1 +0,0 @@
+-twisted.web.client.RedirectAgent and twisted.web.client.BrowserLikeRedirectAgent now properly remove sensitive headers when redirecting to a different origin.
+\ No newline at end of file
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-7.patch twisted-20.3.0/debian/patches/CVE-2022-21712-7.patch
--- twisted-20.3.0/debian/patches/CVE-2022-21712-7.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-21712-7.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,179 @@
+From 9d53c0f8b38340fe5ca3fa1a7df49347c2dea18a Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 14:37:22 -0800
+Subject: [PATCH] tell mypy enough that it can actually find bugs
+
+---
+ src/twisted/web/test/test_agent.py | 88 +++++++++---------------------
+ 1 file changed, 27 insertions(+), 61 deletions(-)
+
+--- a/src/twisted/web/test/test_agent.py
++++ b/src/twisted/web/test/test_agent.py
+@@ -6,77 +6,54 @@ Tests for L{twisted.web.client.Agent} an
+ """
+ import zlib
+ from io import BytesIO
+-from typing import TYPE_CHECKING, Optional
++from twisted.test.iosim import FakeTransport, IOPump
++from twisted.test.proto_helpers import AccumulatingProtocol, EventLoggingObserver, MemoryReactorClock, StringTransport
++from twisted.test.test_sslverify import certificatesForAuthorityAndServer
++from typing import List, Optional, TYPE_CHECKING, Tuple, cast
+ from unittest import SkipTest, skipIf
+ 
+-from zope.interface.declarations import implementer
+-from zope.interface.verify import verifyObject
+ 
+ from twisted.trial.unittest import TestCase, SynchronousTestCase
+ from twisted.web import client, error, http_headers
+-from twisted.web._newclient import RequestNotSent, RequestTransmissionFailed
+-from twisted.web._newclient import ResponseNeverReceived, ResponseFailed
+-from twisted.web._newclient import PotentialDataLoss
+ from twisted.internet import defer, task
+ from twisted.python.failure import Failure
+ from twisted.python.compat import cookielib, intToBytes, unicode
+ from twisted.python.components import proxyForInterface
+ from twisted.internet.task import Clock
+-from twisted.internet.error import ConnectionRefusedError, ConnectionDone
+-from twisted.internet.error import ConnectionLost
++from twisted.internet.error import ConnectionDone, ConnectionLost, ConnectionRefusedError
+ from twisted.internet.protocol import Protocol, Factory
+ from twisted.internet.defer import Deferred, succeed, CancelledError
+ from twisted.internet.endpoints import TCP4ClientEndpoint
+ from twisted.internet.address import IPv4Address, IPv6Address
+ 
+-from twisted.test.iosim import FakeTransport, IOPump
+-from twisted.test.proto_helpers import (
+-    AccumulatingProtocol,
+-    EventLoggingObserver,
+-    MemoryReactorClock,
+-    StringTransport,
+-)
+-from twisted.test.test_sslverify import certificatesForAuthorityAndServer
++from twisted.web._newclient import HTTP11ClientProtocol, PotentialDataLoss, RequestNotSent, RequestTransmissionFailed, Response, ResponseFailed, ResponseNeverReceived
++from twisted.web.client import BrowserLikePolicyForHTTPS, FileBodyProducer, HTTPConnectionPool, HostnameCachingHTTPSPolicy, Request, ResponseDone, URI, _HTTP11ClientFactory
+ 
+-from twisted.web.client import (
+-    URI,
+-    FileBodyProducer,
+-    BrowserLikePolicyForHTTPS,
+-    Request,
+-    HostnameCachingHTTPSPolicy,
+-    HTTPConnectionPool,
+-    ResponseDone,
+-    _HTTP11ClientFactory,
+-)
+-
+-from twisted.web.iweb import (
+-    UNKNOWN_LENGTH,
+-    IAgent,
+-    IBodyProducer,
+-    IPolicyForHTTPS,
+-    IRequest,
+-    IResponse,
+-    IAgentEndpointFactory,
+-)
+ from twisted.web.http_headers import Headers
+-from twisted.web._newclient import HTTP11ClientProtocol, Response
+ 
+ from twisted.internet.interfaces import IOpenSSLClientConnectionCreator
+ from twisted.web.iweb import IPolicyForHTTPS
+ from twisted.python.deprecate import getDeprecationWarningString
+ from incremental import Version
+ 
+-from twisted.web.client import (BrowserLikePolicyForHTTPS,
+-                                HostnameCachingHTTPSPolicy)
+ from twisted.internet.test.test_endpoints import deterministicResolvingReactor
+ from twisted.internet.endpoints import HostnameEndpoint
+-from twisted.web.test.injectionhelpers import (
+-    MethodInjectionTestsMixin,
+-    URIInjectionTestsMixin,
+-)
+ from twisted.web.error import SchemeNotSupported
+ from twisted.logger import globalLogPublisher
+ 
++from twisted.web.iweb import IAgent, IAgentEndpointFactory, IBodyProducer, IPolicyForHTTPS, IRequest, IResponse, UNKNOWN_LENGTH
++from twisted.web.test.injectionhelpers import MethodInjectionTestsMixin, URIInjectionTestsMixin
++from zope.interface.declarations import implementer
++from zope.interface.verify import verifyObject
++
++# Creatively lie to mypy about the nature of inheritance, since dealing with
++# expectations of a mixin class is basically impossible (don't use mixins).
++if TYPE_CHECKING:
++    testMixinClass = TestCase
++    runtimeTestCase = object
++else:
++    testMixinClass = object
++    runtimeTestCase = TestCase
+ 
+ try:
+     from twisted.internet import ssl
+@@ -112,11 +89,10 @@ class StubHTTPProtocol(Protocol):
+         tuple consisting of the request and the L{Deferred} returned from the
+         request method is appended to this list.
+     """
+-    def __init__(self):
+-        self.requests = []
++    def __init__(self) -> None:
++        self.requests: List[Tuple[Request, Deferred[IResponse]]] = []
+         self.state = 'QUIESCENT'
+ 
+-
+     def request(self, request):
+         """
+         Capture the given request for later inspection.
+@@ -2655,11 +2631,6 @@ SENSITIVE_HEADERS = [
+     b"www-authenticate",
+ ]
+ 
+-if TYPE_CHECKING:
+-    testMixinClass = TestCase
+-else:
+-    testMixinClass = object
+-
+ 
+ class _RedirectAgentTestsMixin(testMixinClass):
+     """
+@@ -2696,7 +2667,7 @@ class _RedirectAgentTestsMixin(testMixin
+         crossDomain: bool = False,
+         crossPort: bool = False,
+         requestHeaders: Optional[Headers] = None,
+-    ) -> IRequest:
++    ) -> Request:
+ 
+         """
+         When getting a redirect, L{client.RedirectAgent} follows the URL
+@@ -2784,7 +2755,7 @@ class _RedirectAgentTestsMixin(testMixin
+ 
+ 
+     def _sensitiveHeadersTest(
+-        self, expectedHostHeader: bytes = b"example.com", **crossKwargs: dict
++        self, expectedHostHeader: bytes = b"example.com", **crossKwargs: bool
+     ) -> None:
+         """
+         L{client.RedirectAgent} scrubs sensitive headers when redirecting
+@@ -3066,8 +3037,9 @@ class _RedirectAgentTestsMixin(testMixin
+ 
+ 
+ 
+-class RedirectAgentTests(TestCase, FakeReactorAndConnectMixin,
+-                         _RedirectAgentTestsMixin, AgentTestsMixin):
++class RedirectAgentTests(
++    FakeReactorAndConnectMixin, _RedirectAgentTestsMixin, AgentTestsMixin, runtimeTestCase,
++):
+     """
+     Tests for L{client.RedirectAgent}.
+     """
+@@ -3105,10 +3077,9 @@ class RedirectAgentTests(TestCase, FakeR
+ 
+ 
+ 
+-class BrowserLikeRedirectAgentTests(TestCase,
+-                                    FakeReactorAndConnectMixin,
+-                                    _RedirectAgentTestsMixin,
+-                                    AgentTestsMixin):
++class BrowserLikeRedirectAgentTests(
++    FakeReactorAndConnectMixin, _RedirectAgentTestsMixin, AgentTestsMixin, runtimeTestCase
++):
+     """
+     Tests for L{client.BrowserLikeRedirectAgent}.
+     """
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-8.patch twisted-20.3.0/debian/patches/CVE-2022-21712-8.patch
--- twisted-20.3.0/debian/patches/CVE-2022-21712-8.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-21712-8.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,134 @@
+From adadd2c6a49a9b5f9b41a1f125004b68449d2fb5 Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 14:40:20 -0800
+Subject: [PATCH] really need to fix my editor to agree with our black / isort
+ config
+
+---
+ src/twisted/web/test/test_agent.py | 72 ++++++++++++++++++++++++------
+ 1 file changed, 58 insertions(+), 14 deletions(-)
+
+--- a/src/twisted/web/test/test_agent.py
++++ b/src/twisted/web/test/test_agent.py
+@@ -4,14 +4,14 @@
+ """
+ Tests for L{twisted.web.client.Agent} and related new client APIs.
+ """
++
+ import zlib
+ from io import BytesIO
+-from twisted.test.iosim import FakeTransport, IOPump
+-from twisted.test.proto_helpers import AccumulatingProtocol, EventLoggingObserver, MemoryReactorClock, StringTransport
+-from twisted.test.test_sslverify import certificatesForAuthorityAndServer
+-from typing import List, Optional, TYPE_CHECKING, Tuple, cast
++from typing import TYPE_CHECKING, List, Optional, Tuple, cast
+ from unittest import SkipTest, skipIf
+ 
++from zope.interface.declarations import implementer
++from zope.interface.verify import verifyObject
+ 
+ from twisted.trial.unittest import TestCase, SynchronousTestCase
+ from twisted.web import client, error, http_headers
+@@ -20,19 +20,18 @@ from twisted.python.failure import Failu
+ from twisted.python.compat import cookielib, intToBytes, unicode
+ from twisted.python.components import proxyForInterface
+ from twisted.internet.task import Clock
+-from twisted.internet.error import ConnectionDone, ConnectionLost, ConnectionRefusedError
++from twisted.internet.error import (
++    ConnectionDone,
++    ConnectionLost,
++    ConnectionRefusedError,
++)
++
+ from twisted.internet.protocol import Protocol, Factory
+ from twisted.internet.defer import Deferred, succeed, CancelledError
+ from twisted.internet.endpoints import TCP4ClientEndpoint
+ from twisted.internet.address import IPv4Address, IPv6Address
+-
+-from twisted.web._newclient import HTTP11ClientProtocol, PotentialDataLoss, RequestNotSent, RequestTransmissionFailed, Response, ResponseFailed, ResponseNeverReceived
+-from twisted.web.client import BrowserLikePolicyForHTTPS, FileBodyProducer, HTTPConnectionPool, HostnameCachingHTTPSPolicy, Request, ResponseDone, URI, _HTTP11ClientFactory
+-
+ from twisted.web.http_headers import Headers
+-
+ from twisted.internet.interfaces import IOpenSSLClientConnectionCreator
+-from twisted.web.iweb import IPolicyForHTTPS
+ from twisted.python.deprecate import getDeprecationWarningString
+ from incremental import Version
+ 
+@@ -40,11 +39,48 @@ from twisted.internet.test.test_endpoint
+ from twisted.internet.endpoints import HostnameEndpoint
+ from twisted.web.error import SchemeNotSupported
+ from twisted.logger import globalLogPublisher
++from twisted.test.iosim import FakeTransport, IOPump
++from twisted.test.proto_helpers import (
++    AccumulatingProtocol,
++    EventLoggingObserver,
++    MemoryReactorClock,
++    StringTransport,
++)
++from twisted.test.test_sslverify import certificatesForAuthorityAndServer
++
++from twisted.web._newclient import (
++    HTTP11ClientProtocol,
++    PotentialDataLoss,
++    RequestNotSent,
++    RequestTransmissionFailed,
++    Response,
++    ResponseFailed,
++    ResponseNeverReceived,
++)
++from twisted.web.client import (
++    URI,
++    BrowserLikePolicyForHTTPS,
++    FileBodyProducer,
++    HostnameCachingHTTPSPolicy,
++    HTTPConnectionPool,
++    Request,
++    ResponseDone,
++    _HTTP11ClientFactory,
++)
++from twisted.web.iweb import (
++    UNKNOWN_LENGTH,
++    IAgent,
++    IAgentEndpointFactory,
++    IBodyProducer,
++    IPolicyForHTTPS,
++    IRequest,
++    IResponse,
++)
++from twisted.web.test.injectionhelpers import (
++    MethodInjectionTestsMixin,
++    URIInjectionTestsMixin,
++)
+ 
+-from twisted.web.iweb import IAgent, IAgentEndpointFactory, IBodyProducer, IPolicyForHTTPS, IRequest, IResponse, UNKNOWN_LENGTH
+-from twisted.web.test.injectionhelpers import MethodInjectionTestsMixin, URIInjectionTestsMixin
+-from zope.interface.declarations import implementer
+-from zope.interface.verify import verifyObject
+ 
+ # Creatively lie to mypy about the nature of inheritance, since dealing with
+ # expectations of a mixin class is basically impossible (don't use mixins).
+@@ -3038,7 +3074,10 @@ class _RedirectAgentTestsMixin(testMixin
+ 
+ 
+ class RedirectAgentTests(
+-    FakeReactorAndConnectMixin, _RedirectAgentTestsMixin, AgentTestsMixin, runtimeTestCase,
++    FakeReactorAndConnectMixin,
++    _RedirectAgentTestsMixin,
++    AgentTestsMixin,
++    runtimeTestCase,
+ ):
+     """
+     Tests for L{client.RedirectAgent}.
+@@ -3078,7 +3117,10 @@ class RedirectAgentTests(
+ 
+ 
+ class BrowserLikeRedirectAgentTests(
+-    FakeReactorAndConnectMixin, _RedirectAgentTestsMixin, AgentTestsMixin, runtimeTestCase
++    FakeReactorAndConnectMixin,
++    _RedirectAgentTestsMixin,
++    AgentTestsMixin,
++    runtimeTestCase,
+ ):
+     """
+     Tests for L{client.BrowserLikeRedirectAgent}.
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21712-9.patch twisted-20.3.0/debian/patches/CVE-2022-21712-9.patch
--- twisted-20.3.0/debian/patches/CVE-2022-21712-9.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-21712-9.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,28 @@
+From 4eda9eabbba8ba2ccc45daad7208ff3db25ac348 Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 14:55:10 -0800
+Subject: [PATCH] lint fix
+
+---
+ src/twisted/web/test/test_agent.py | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/src/twisted/web/test/test_agent.py
++++ b/src/twisted/web/test/test_agent.py
+@@ -7,7 +7,7 @@ Tests for L{twisted.web.client.Agent} an
+ 
+ import zlib
+ from io import BytesIO
+-from typing import TYPE_CHECKING, List, Optional, Tuple, cast
++from typing import TYPE_CHECKING, List, Optional, Tuple
+ from unittest import SkipTest, skipIf
+ 
+ from zope.interface.declarations import implementer
+@@ -73,7 +73,6 @@ from twisted.web.iweb import (
+     IAgentEndpointFactory,
+     IBodyProducer,
+     IPolicyForHTTPS,
+-    IRequest,
+     IResponse,
+ )
+ from twisted.web.test.injectionhelpers import (
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21716-1.patch twisted-20.3.0/debian/patches/CVE-2022-21716-1.patch
--- twisted-20.3.0/debian/patches/CVE-2022-21716-1.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-21716-1.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,66 @@
+From de90dfe1519e996dd150de751c670f8e03daa089 Mon Sep 17 00:00:00 2001
+From: Adi Roiban <adi.roiban@chevah.com>
+Date: Mon, 24 Jan 2022 19:09:04 +0000
+Subject: [PATCH] Initial fix for Twisted version string DoS.
+
+---
+ src/twisted/conch/ssh/transport.py       |  9 +++++++++
+ src/twisted/conch/test/test_transport.py | 22 ++++++++++++++++++++++
+ src/twisted/newsfragments/10284.bugfix   |  2 ++
+ 3 files changed, 33 insertions(+)
+ create mode 100644 src/twisted/newsfragments/10284.bugfix
+
+--- a/src/twisted/conch/ssh/transport.py
++++ b/src/twisted/conch/ssh/transport.py
+@@ -692,6 +692,15 @@ class SSHTransportBase(protocol.Protocol
+         """
+         self.buf = self.buf + data
+         if not self.gotVersion:
++
++            if len(self.buf) > 4096:
++                self.sendDisconnect(
++                    DISCONNECT_CONNECTION_LOST,
++                    b"Peer version string longer than 4KB. "
++                    b"Preventing a deny of service attack.",
++                )
++                return
++
+             if self.buf.find(b'\n', self.buf.find(b'SSH-')) == -1:
+                 return
+ 
+--- a/src/twisted/conch/test/test_transport.py
++++ b/src/twisted/conch/test/test_transport.py
+@@ -548,6 +548,28 @@ class BaseSSHTransportTests(BaseSSHTrans
+         self.assertRegex(softwareVersion, softwareVersionRegex)
+ 
+ 
++    def test_dataReceiveVersionNotSentMemoryDOS(self):
++        """
++        When the peer is not sending its SSH version but keeps sending data,
++        the connection is disconnected after 4KB to prevent buffering to
++        much and running our of memory.
++        """
++        sut = MockTransportBase()
++        sut.makeConnection(self.transport)
++
++        sut.dataReceived(b"SSH-bla-bla-bla")
++
++        sut.dataReceived(b"more-bla-bla-bla" * 100)
++
++        self.assertFalse(self.transport.disconnecting)
++
++        sut.dataReceived(b"more-bla-bla-bla" * 1000)
++
++        # Once a lot of data is received without an SSH version string,
++        # the transport is disconnected.
++        self.assertTrue(self.transport.disconnecting)
++        self.assertIn(b"Preventing a deny of service attack", self.transport.value())
++
+     def test_sendPacketPlain(self):
+         """
+         Test that plain (unencrypted, uncompressed) packets are sent
+--- /dev/null
++++ b/src/twisted/newsfragments/10284.bugfix
+@@ -0,0 +1,2 @@
++twisted.conch.ssh.transport.SSHTransportBase now disconnects the remote peer if the
++SSH version string is not sent in the first 4096 bytes.
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21716-2.patch twisted-20.3.0/debian/patches/CVE-2022-21716-2.patch
--- twisted-20.3.0/debian/patches/CVE-2022-21716-2.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-21716-2.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,49 @@
+From 9b98116372f5d211ddb9f68916d4ae73bf3c8da7 Mon Sep 17 00:00:00 2001
+From: Adi Roiban <adi.roiban@chevah.com>
+Date: Mon, 24 Jan 2022 23:53:54 +0000
+Subject: [PATCH] Update after review.
+
+---
+ src/twisted/conch/ssh/transport.py       |  2 +-
+ src/twisted/conch/test/test_transport.py | 14 +++++++-------
+ 2 files changed, 8 insertions(+), 8 deletions(-)
+
+--- a/src/twisted/conch/ssh/transport.py
++++ b/src/twisted/conch/ssh/transport.py
+@@ -697,7 +697,7 @@ class SSHTransportBase(protocol.Protocol
+                 self.sendDisconnect(
+                     DISCONNECT_CONNECTION_LOST,
+                     b"Peer version string longer than 4KB. "
+-                    b"Preventing a deny of service attack.",
++                    b"Preventing a denial of service attack.",
+                 )
+                 return
+ 
+--- a/src/twisted/conch/test/test_transport.py
++++ b/src/twisted/conch/test/test_transport.py
+@@ -557,18 +557,18 @@ class BaseSSHTransportTests(BaseSSHTrans
+         sut = MockTransportBase()
+         sut.makeConnection(self.transport)
+ 
+-        sut.dataReceived(b"SSH-bla-bla-bla")
+-
+-        sut.dataReceived(b"more-bla-bla-bla" * 100)
+-
++        # Data can be received over multiple chunks.
++        sut.dataReceived(b"SSH-2-Server-Identifier")
++        sut.dataReceived(b"1234567890" * 406)
++        sut.dataReceived(b"1235678")
+         self.assertFalse(self.transport.disconnecting)
+ 
+-        sut.dataReceived(b"more-bla-bla-bla" * 1000)
+-
++        # Here we are going over the limit.
++        sut.dataReceived(b"1234567")
+         # Once a lot of data is received without an SSH version string,
+         # the transport is disconnected.
+         self.assertTrue(self.transport.disconnecting)
+-        self.assertIn(b"Preventing a deny of service attack", self.transport.value())
++        self.assertIn(b"Preventing a denial of service attack", self.transport.value())
+ 
+     def test_sendPacketPlain(self):
+         """
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-21716-3.patch twisted-20.3.0/debian/patches/CVE-2022-21716-3.patch
--- twisted-20.3.0/debian/patches/CVE-2022-21716-3.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-21716-3.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,20 @@
+From a4523b444760f07e609636264a61a2a07ca0bde5 Mon Sep 17 00:00:00 2001
+From: Adi Roiban <adi.roiban@chevah.com>
+Date: Tue, 8 Feb 2022 14:01:10 +0000
+Subject: [PATCH] Fix typo.
+
+---
+ src/twisted/conch/test/test_transport.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/src/twisted/conch/test/test_transport.py
++++ b/src/twisted/conch/test/test_transport.py
+@@ -551,7 +551,7 @@ class BaseSSHTransportTests(BaseSSHTrans
+     def test_dataReceiveVersionNotSentMemoryDOS(self):
+         """
+         When the peer is not sending its SSH version but keeps sending data,
+-        the connection is disconnected after 4KB to prevent buffering to
++        the connection is disconnected after 4KB to prevent buffering too
+         much and running our of memory.
+         """
+         sut = MockTransportBase()
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-1.patch twisted-20.3.0/debian/patches/CVE-2022-24801-1.patch
--- twisted-20.3.0/debian/patches/CVE-2022-24801-1.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-24801-1.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,128 @@
+From: Tom Most <twm@freecog.net>
+Date: Sat, 5 Mar 2022 23:26:55 -0800
+Subject: Some tests for GHSA-c2jg-hw38-jrqq
+
+---
+ src/twisted/web/test/test_http.py | 102 ++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 102 insertions(+)
+
+diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py
+index ab1cac2..4acc0e7 100644
+--- a/src/twisted/web/test/test_http.py
++++ b/src/twisted/web/test/test_http.py
+@@ -1693,6 +1693,56 @@ class ParsingTests(unittest.TestCase):
+         )
+ 
+ 
++    def test_headerStripWhitespace(self):
++        """
++        Leading and trailing space and tab characters are stripped from
++        headers. Other forms of whitespace are preserved.
++
++        See RFC 7230 section 3.2.3 and 3.2.4.
++        """
++        processed = []
++
++        class MyRequest(http.Request):
++            def process(self):
++                processed.append(self)
++                self.finish()
++
++        requestLines = [
++            b"GET / HTTP/1.0",
++            b"spaces:   spaces were stripped   ",
++            b"tabs: \t\ttabs were stripped\t\t",
++            b"spaces-and-tabs: \t \t spaces and tabs were stripped\t \t",
++            b"line-tab:   \v vertical tab was preserved\v\t",
++            b"form-feed: \f form feed was preserved \f  ",
++            b"",
++            b"",
++        ]
++
++        self.runRequest(b"\n".join(requestLines), MyRequest, 0)
++        [request] = processed
++        # All leading and trailing whitespace is stripped from the
++        # header-value.
++        self.assertEqual(
++            request.requestHeaders.getRawHeaders(b"spaces"),
++            [b"spaces were stripped"],
++        )
++        self.assertEqual(
++            request.requestHeaders.getRawHeaders(b"tabs"),
++            [b"tabs were stripped"],
++        )
++        self.assertEqual(
++            request.requestHeaders.getRawHeaders(b"spaces-and-tabs"),
++            [b"spaces and tabs were stripped"],
++        )
++        self.assertEqual(
++            request.requestHeaders.getRawHeaders(b"line-tab"),
++            [b"\v vertical tab was preserved\v"],
++        )
++        self.assertEqual(
++            request.requestHeaders.getRawHeaders(b"form-feed"),
++            [b"\f form feed was preserved \f"],
++        )
++
+     def test_tooManyHeaders(self):
+         """
+         C{HTTPChannel} enforces a limit of C{HTTPChannel.maxHeaders} on the
+@@ -2238,6 +2288,58 @@ Hello,
+         ])
+ 
+ 
++    def test_contentLengthMalformed(self):
++        """
++        A request with a non-integer C{Content-Length} header fails with a 400
++        response without calling L{Request.process}.
++        """
++        self.assertRequestRejected(
++            [
++                b"GET /a HTTP/1.1",
++                b"Content-Length: MORE THAN NINE THOUSAND!",
++                b"Host: host.invalid",
++                b"",
++                b"",
++                b"x" * 9001,
++            ]
++        )
++
++    def test_contentLengthTooPositive(self):
++        """
++        A request with a C{Content-Length} header that begins with a L{+} fails
++        with a 400 response without calling L{Request.process}.
++
++        This is a potential request smuggling vector: see GHSA-c2jg-hw38-jrqq.
++        """
++        self.assertRequestRejected(
++            [
++                b"GET /a HTTP/1.1",
++                b"Content-Length: +100",
++                b"Host: host.invalid",
++                b"",
++                b"",
++                b"x" * 100,
++            ]
++        )
++
++    def test_contentLengthNegative(self):
++        """
++        A request with a C{Content-Length} header that is negative fails with
++        a 400 response without calling L{Request.process}.
++
++        This is a potential request smuggling vector: see GHSA-c2jg-hw38-jrqq.
++        """
++        self.assertRequestRejected(
++            [
++                b"GET /a HTTP/1.1",
++                b"Content-Length: -100",
++                b"Host: host.invalid",
++                b"",
++                b"",
++                b"x" * 200,
++            ]
++        )
++
+     def test_duplicateContentLengthsWithPipelinedRequests(self):
+         """
+         Two pipelined requests, the first of which includes multiple
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-2.patch twisted-20.3.0/debian/patches/CVE-2022-24801-2.patch
--- twisted-20.3.0/debian/patches/CVE-2022-24801-2.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-24801-2.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,59 @@
+From: Tom Most <twm@freecog.net>
+Date: Mon, 7 Mar 2022 00:02:55 -0800
+Subject: Replace obs-fold with a single space
+
+---
+ src/twisted/web/http.py           |  2 +-
+ src/twisted/web/test/test_http.py | 13 +++++++++----
+ 2 files changed, 10 insertions(+), 5 deletions(-)
+
+diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
+index 78f4a36..b5c0b25 100644
+--- a/src/twisted/web/http.py
++++ b/src/twisted/web/http.py
+@@ -2157,7 +2157,7 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin):
+                 self.setRawMode()
+         elif line[0] in b' \t':
+             # Continuation of a multi line header.
+-            self.__header = self.__header + b'\n' + line
++            self.__header += b" " + line.lstrip(b" \t")
+         # Regular header line.
+         # Processing of header line is delayed to allow accumulating multi
+         # line headers.
+diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py
+index 4acc0e7..e4ea2ef 100644
+--- a/src/twisted/web/test/test_http.py
++++ b/src/twisted/web/test/test_http.py
+@@ -1644,7 +1644,12 @@ class ParsingTests(unittest.TestCase):
+         Line folded headers are handled by L{HTTPChannel} by replacing each
+         fold with a single space by the time they are made available to the
+         L{Request}. Any leading whitespace in the folded lines of the header
+-        value is preserved.
++        value is replaced with a single space, per:
++
++            A server that receives an obs-fold in a request message ... MUST
++            ... replace each received obs-fold with one or more SP octets prior
++            to interpreting the field value or forwarding the message
++            downstream.
+ 
+         See RFC 7230 section 3.2.4.
+         """
+@@ -1681,15 +1686,15 @@ class ParsingTests(unittest.TestCase):
+         )
+         self.assertEqual(
+             request.requestHeaders.getRawHeaders(b"space"),
+-            [b"space  space"],
++            [b"space space"],
+         )
+         self.assertEqual(
+             request.requestHeaders.getRawHeaders(b"spaces"),
+-            [b"spaces   spaces    spaces"],
++            [b"spaces spaces spaces"],
+         )
+         self.assertEqual(
+             request.requestHeaders.getRawHeaders(b"tab"),
+-            [b"t \ta \tb"],
++            [b"t a b"],
+         )
+ 
+ 
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-3.patch twisted-20.3.0/debian/patches/CVE-2022-24801-3.patch
--- twisted-20.3.0/debian/patches/CVE-2022-24801-3.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-24801-3.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,21 @@
+From: Tom Most <twm@freecog.net>
+Date: Mon, 7 Mar 2022 00:03:50 -0800
+Subject: Strip only spaces and tabs from header values
+
+---
+ src/twisted/web/http.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
+index b5c0b25..1ff77ac 100644
+--- a/src/twisted/web/http.py
++++ b/src/twisted/web/http.py
+@@ -2240,7 +2240,7 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin):
+             return False
+ 
+         header = header.lower()
+-        data = data.strip()
++        data = data.strip(b" \t")
+ 
+         if not self._maybeChooseTransferDecoder(header, data):
+             return False
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-4.patch twisted-20.3.0/debian/patches/CVE-2022-24801-4.patch
--- twisted-20.3.0/debian/patches/CVE-2022-24801-4.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-24801-4.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,21 @@
+From: Tom Most <twm@freecog.net>
+Date: Mon, 7 Mar 2022 00:32:14 -0800
+Subject: Reject non-digit Content-Length
+
+---
+ src/twisted/web/http.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
+index 1ff77ac..e62e17b 100644
+--- a/src/twisted/web/http.py
++++ b/src/twisted/web/http.py
+@@ -2186,6 +2186,8 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin):
+ 
+         # Can this header determine the length?
+         if header == b'content-length':
++            if not data.isdigit():
++                return fail()
+             try:
+                 length = int(data)
+             except ValueError:
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-5.patch twisted-20.3.0/debian/patches/CVE-2022-24801-5.patch
--- twisted-20.3.0/debian/patches/CVE-2022-24801-5.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-24801-5.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,69 @@
+From: Tom Most <twm@freecog.net>
+Date: Sun, 13 Mar 2022 23:19:39 -0700
+Subject: Test for malformed chunk size and extensions
+
+---
+ src/twisted/web/test/test_http.py | 36 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 36 insertions(+)
+
+diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py
+index e4ea2ef..9079279 100644
+--- a/src/twisted/web/test/test_http.py
++++ b/src/twisted/web/test/test_http.py
+@@ -1299,6 +1299,23 @@ class ChunkedTransferEncodingTests(unittest.TestCase):
+         self.assertEqual(L, [b'abc'])
+ 
+ 
++    def test_extensionsMalformed(self):
++        """
++        L{_ChunkedTransferDecoder.dataReceived} raises
++        L{_MalformedChunkedDataError} when the chunk extension fields contain
++        invalid characters.
++
++        This is a potential request smuggling vector: see GHSA-c2jg-hw38-jrqq.
++        """
++        for b in [*range(0, 0x09), *range(0x10, 0x21), *range(0x74, 0x80)]:
++            data = b"3; " + bytes((b,)) + b"\r\nabc\r\n"
++            p = http._ChunkedTransferDecoder(
++                lambda b: None,  # pragma: nocov
++                lambda b: None,  # pragma: nocov
++            )
++            self.assertRaises(http._MalformedChunkedDataError, p.dataReceived, data)
++
++
+     def test_finish(self):
+         """
+         L{_ChunkedTransferDecoder.dataReceived} interprets a zero-length
+@@ -1376,6 +1393,23 @@ class ChunkedTransferEncodingTests(unittest.TestCase):
+         self.assertEqual(successes, [True])
+ 
+ 
++    def test_malformedChunkSizeHex(self):
++        """
++        L{_ChunkedTransferDecoder.dataReceived} raises
++        L{_MalformedChunkedDataError} when the chunk size is prefixed with
++        "0x", as if it were a Python integer literal.
++
++        This is a potential request smuggling vector: see GHSA-c2jg-hw38-jrqq.
++        """
++        p = http._ChunkedTransferDecoder(
++            lambda b: None,  # pragma: nocov
++            lambda b: None,  # pragma: nocov
++        )
++        self.assertRaises(
++            http._MalformedChunkedDataError, p.dataReceived, b"0x3\r\nabc\r\n"
++        )
++
++
+ 
+ class ChunkingTests(unittest.TestCase, ResponseTestMixin):
+ 
+@@ -1387,6 +1421,8 @@ class ChunkingTests(unittest.TestCase, ResponseTestMixin):
+             chunked = b''.join(http.toChunk(s))
+             self.assertEqual((s, b''), http.fromChunk(chunked))
+         self.assertRaises(ValueError, http.fromChunk, b'-5\r\nmalformed!\r\n')
++        self.assertRaises(ValueError, http.fromChunk, b"0xa\r\nmalformed!\r\n")
++        self.assertRaises(ValueError, http.fromChunk, b"0XA\r\nmalformed!\r\n")
+ 
+     def testConcatenatedChunks(self):
+         chunked = b''.join([b''.join(http.toChunk(t)) for t in self.strings])
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-6.patch twisted-20.3.0/debian/patches/CVE-2022-24801-6.patch
--- twisted-20.3.0/debian/patches/CVE-2022-24801-6.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-24801-6.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,114 @@
+From: Tom Most <twm@freecog.net>
+Date: Sun, 13 Mar 2022 23:51:52 -0700
+Subject: Reject malformed chunk sizes
+
+---
+ src/twisted/web/http.py           | 32 ++++++++++++++++++++++++++++---
+ src/twisted/web/test/test_http.py | 40 +++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 69 insertions(+), 3 deletions(-)
+
+diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
+index e62e17b..f9d61ab 100644
+--- a/src/twisted/web/http.py
++++ b/src/twisted/web/http.py
+@@ -338,7 +338,33 @@ def toChunk(data):
+ 
+ 
+ 
+-def fromChunk(data):
++def _ishexdigits(b: bytes) -> bool:
++    """
++    Is the string case-insensitively hexidecimal?
++
++    It must be composed of one or more characters in the ranges a-f, A-F
++    and 0-9.
++    """
++    for c in b:
++        if c not in b'0123456789abcdefABCDEF':
++            return False
++    return bool(b)
++
++
++def _hexint(b: bytes) -> int:
++    """
++    Decode a hexadecimal integer.
++
++    Unlike L{int(b, 16)}, this raises L{ValueError} when the integer has
++    a prefix like C{b'0x'}, C{b'+'}, or C{b'-'}, which is desirable when
++    parsing network protocols.
++    """
++    if not _ishexdigits(b):
++        raise ValueError(b)
++    return int(b, 16)
++
++
++def fromChunk(data: bytes):
+     """
+     Convert chunk to string.
+ 
+@@ -350,7 +376,7 @@ def fromChunk(data):
+         byte string.
+     """
+     prefix, rest = data.split(b'\r\n', 1)
+-    length = int(prefix, 16)
++    length = _hexint(prefix)
+     if length < 0:
+         raise ValueError("Chunk length must be >= 0, not %d" % (length,))
+     if rest[length:length + 2] != b'\r\n':
+@@ -1827,7 +1853,7 @@ class _ChunkedTransferDecoder(object):
+             line, rest = data.split(b'\r\n', 1)
+             parts = line.split(b';')
+             try:
+-                self.length = int(parts[0], 16)
++                self.length = _hexint(parts[0])
+             except ValueError:
+                 raise _MalformedChunkedDataError(
+                     "Chunk-size must be an integer.")
+diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py
+index 9079279..ba400c4 100644
+--- a/src/twisted/web/test/test_http.py
++++ b/src/twisted/web/test/test_http.py
+@@ -4341,3 +4341,43 @@ class HTTPClientSanitizationTests(unittest.SynchronousTestCase):
+                 transport.value().splitlines(),
+                 [b": ".join([sanitizedBytes, sanitizedBytes])]
+             )
++
++
++class HexHelperTests(unittest.SynchronousTestCase):
++    """
++    Test the L{http._hexint} and L{http._ishexdigits} helper functions.
++    """
++
++    badStrings = (b"", b"0x1234", b"feds", b"-123" b"+123")
++
++    def test_isHex(self):
++        """
++        L{_ishexdigits()} returns L{True} for nonempy bytestrings containing
++        hexadecimal digits.
++        """
++        for s in (b"10", b"abcdef", b"AB1234", b"fed", b"123467890"):
++            self.assertIs(True, http._ishexdigits(s))
++
++    def test_decodes(self):
++        """
++        L{_hexint()} returns the integer equivalent of the input.
++        """
++        self.assertEqual(10, http._hexint(b"a"))
++        self.assertEqual(0x10, http._hexint(b"10"))
++        self.assertEqual(0xABCD123, http._hexint(b"abCD123"))
++
++    def test_isNotHex(self):
++        """
++        L{_ishexdigits()} returns L{False} for bytestrings that don't contain
++        hexadecimal digits, including the empty string.
++        """
++        for s in self.badStrings:
++            self.assertIs(False, http._ishexdigits(s))
++
++    def test_decodeNotHex(self):
++        """
++        L{_hexint()} raises L{ValueError} for bytestrings that can't
++        be decoded.
++        """
++        for s in self.badStrings:
++            self.assertRaises(ValueError, http._hexint, s)
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-7.patch twisted-20.3.0/debian/patches/CVE-2022-24801-7.patch
--- twisted-20.3.0/debian/patches/CVE-2022-24801-7.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-24801-7.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,22 @@
+From: Tom Most <twm@freecog.net>
+Date: Sun, 13 Mar 2022 23:55:26 -0700
+Subject: We should deprecate http.fromChunk
+
+---
+ src/twisted/web/http.py | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
+index f9d61ab..a7fcbc1 100644
+--- a/src/twisted/web/http.py
++++ b/src/twisted/web/http.py
+@@ -368,6 +368,9 @@ def fromChunk(data: bytes):
+     """
+     Convert chunk to string.
+ 
++    Note that this function is not specification compliant: it doesn't handle
++    chunk extensions.
++
+     @type data: C{bytes}
+ 
+     @return: tuple of (result, remaining) - both C{bytes}.
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-8.patch twisted-20.3.0/debian/patches/CVE-2022-24801-8.patch
--- twisted-20.3.0/debian/patches/CVE-2022-24801-8.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-24801-8.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,100 @@
+From: Tom Most <twm@freecog.net>
+Date: Sun, 27 Mar 2022 22:17:30 -0700
+Subject: Correct chunk extension byte validation
+
+Go back to the RFC to figure out the correct allowed ranges.
+---
+ src/twisted/web/http.py           | 46 ++++++++++++++++++++++++++++++++++++++-
+ src/twisted/web/test/test_http.py |  8 ++++++-
+ 2 files changed, 52 insertions(+), 2 deletions(-)
+
+diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
+index a7fcbc1..baf6b87 100644
+--- a/src/twisted/web/http.py
++++ b/src/twisted/web/http.py
+@@ -346,7 +346,7 @@ def _ishexdigits(b: bytes) -> bool:
+     and 0-9.
+     """
+     for c in b:
+-        if c not in b'0123456789abcdefABCDEF':
++        if c not in b"0123456789abcdefABCDEF":
+             return False
+     return bool(b)
+ 
+@@ -1804,6 +1804,46 @@ class _IdentityTransferDecoder(object):
+             raise _DataLoss()
+ 
+ 
++_chunkExtChars = (
++    b"\t !\"#$%&'()*+,-./0123456789:;<=>?@"
++    b"ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`"
++    b"abcdefghijklmnopqrstuvwxyz{|}~"
++    b"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
++    b"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
++    b"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
++    b"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
++    b"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
++    b"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
++    b"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
++    b"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
++)
++"""
++Characters that are valid in a chunk extension.
++
++See RFC 7230 section 4.1.1:
++
++     chunk-ext      = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
++
++     chunk-ext-name = token
++     chunk-ext-val  = token / quoted-string
++
++Section 3.2.6:
++
++     token          = 1*tchar
++
++     tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
++                    / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
++                    / DIGIT / ALPHA
++                    ; any VCHAR, except delimiters
++
++     quoted-string  = DQUOTE *( qdtext / quoted-pair ) DQUOTE
++     qdtext         = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text
++     obs-text       = %x80-FF
++
++We don't check if chunk extensions are well-formed beyond validating that they
++don't contain characters outside this range.
++"""
++
+ 
+ class _ChunkedTransferDecoder(object):
+     """
+@@ -1860,6 +1900,10 @@ class _ChunkedTransferDecoder(object):
+             except ValueError:
+                 raise _MalformedChunkedDataError(
+                     "Chunk-size must be an integer.")
++            if len(parts) > 1 and parts[1].translate(None, _chunkExtChars) != b"":
++                raise _MalformedChunkedDataError(
++                    f"Invalid characters in chunk extensions: {parts[1]!r}."
++                )
+             if self.length == 0:
+                 self.state = 'TRAILER'
+             else:
+diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py
+index ba400c4..5e31cee 100644
+--- a/src/twisted/web/test/test_http.py
++++ b/src/twisted/web/test/test_http.py
+@@ -1307,7 +1307,13 @@ class ChunkedTransferEncodingTests(unittest.TestCase):
+ 
+         This is a potential request smuggling vector: see GHSA-c2jg-hw38-jrqq.
+         """
+-        for b in [*range(0, 0x09), *range(0x10, 0x21), *range(0x74, 0x80)]:
++        invalidControl = (
++            b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\n\x0b\x0c\r\x0e\x0f"
++            b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
++        )
++        invalidDelimiter = b"\\"
++        invalidDel = b"\x7f"
++        for b in invalidControl + invalidDelimiter + invalidDel:
+             data = b"3; " + bytes((b,)) + b"\r\nabc\r\n"
+             p = http._ChunkedTransferDecoder(
+                 lambda b: None,  # pragma: nocov
diff -Nru twisted-20.3.0/debian/patches/CVE-2022-24801-9.patch twisted-20.3.0/debian/patches/CVE-2022-24801-9.patch
--- twisted-20.3.0/debian/patches/CVE-2022-24801-9.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/CVE-2022-24801-9.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,38 @@
+From: Tom Most <twm@freecog.net>
+Date: Fri, 1 Apr 2022 20:47:59 -0700
+Subject: Address review feedback
+
+---
+ src/twisted/web/http.py | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
+index baf6b87..f5088ab 100644
+--- a/src/twisted/web/http.py
++++ b/src/twisted/web/http.py
+@@ -348,7 +348,7 @@ def _ishexdigits(b: bytes) -> bool:
+     for c in b:
+         if c not in b"0123456789abcdefABCDEF":
+             return False
+-    return bool(b)
++    return b != b""
+ 
+ 
+ def _hexint(b: bytes) -> int:
+@@ -1820,14 +1820,14 @@ _chunkExtChars = (
+ """
+ Characters that are valid in a chunk extension.
+ 
+-See RFC 7230 section 4.1.1:
++See RFC 7230 section 4.1.1::
+ 
+      chunk-ext      = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
+ 
+      chunk-ext-name = token
+      chunk-ext-val  = token / quoted-string
+ 
+-Section 3.2.6:
++And section 3.2.6::
+ 
+      token          = 1*tchar
+ 
diff -Nru twisted-20.3.0/debian/patches/series twisted-20.3.0/debian/patches/series
--- twisted-20.3.0/debian/patches/series	2021-04-24 12:36:24.000000000 -0400
+++ twisted-20.3.0/debian/patches/series	2022-05-05 09:59:26.000000000 -0400
@@ -22,3 +22,26 @@
 0023-Merge-9801-rodrigc-cgi-Change-import-of-cgi.parse_qs.patch
 0024-fixed-corrupted-iqmp-value-in-test-RSA-key.patch
 0025-Skip-failing-twisted.web.test.test_http.QueryArgumen.patch
+CVE-2022-21712-1.patch
+CVE-2022-21712-2.patch
+CVE-2022-21712-3.patch
+CVE-2022-21712-4.patch
+CVE-2022-21712-5.patch
+CVE-2022-21712-6.patch
+CVE-2022-21712-7.patch
+CVE-2022-21712-8.patch
+CVE-2022-21712-9.patch
+CVE-2022-21712-10.patch
+CVE-2022-21716-1.patch
+CVE-2022-21716-2.patch
+CVE-2022-21716-3.patch
+CVE-2022-24801-1.patch
+CVE-2022-24801-2.patch
+CVE-2022-24801-3.patch
+CVE-2022-24801-4.patch
+CVE-2022-24801-5.patch
+CVE-2022-24801-6.patch
+CVE-2022-24801-7.patch
+CVE-2022-24801-8.patch
+CVE-2022-24801-9.patch
+Tests-remove-spurious-test-for-illegal-whitespace-in-xmlns.patch
diff -Nru twisted-20.3.0/debian/patches/Tests-remove-spurious-test-for-illegal-whitespace-in-xmlns.patch twisted-20.3.0/debian/patches/Tests-remove-spurious-test-for-illegal-whitespace-in-xmlns.patch
--- twisted-20.3.0/debian/patches/Tests-remove-spurious-test-for-illegal-whitespace-in-xmlns.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-20.3.0/debian/patches/Tests-remove-spurious-test-for-illegal-whitespace-in-xmlns.patch	2022-05-05 09:59:26.000000000 -0400
@@ -0,0 +1,37 @@
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 27 Mar 2022 18:03:49 -0700
+Subject: remove spurious test for illegal whitespace in xmlns
+
+Origin: upstream, https://github.com/twisted/twisted/commit/6b4bbf9040abd8e5c8feae026c4a6483c7f72506
+---
+ src/twisted/newsfragments/10318.misc  |  0
+ src/twisted/words/test/test_domish.py | 12 ------------
+ 2 files changed, 12 deletions(-)
+ create mode 100644 src/twisted/newsfragments/10318.misc
+
+diff --git a/src/twisted/newsfragments/10318.misc b/src/twisted/newsfragments/10318.misc
+new file mode 100644
+index 0000000..e69de29
+diff --git a/src/twisted/words/test/test_domish.py b/src/twisted/words/test/test_domish.py
+index cd16e3a..4e37171 100644
+--- a/src/twisted/words/test/test_domish.py
++++ b/src/twisted/words/test/test_domish.py
+@@ -338,18 +338,6 @@ class DomishStreamTestsMixin:
+         self.assertEqual(self.elements[0].child2.uri, '')
+ 
+ 
+-    def test_namespaceWithWhitespace(self):
+-        """
+-        Whitespace in an xmlns value is preserved in the resulting node's C{uri}
+-        attribute.
+-        """
+-        xml = b"<root xmlns:foo=' bar baz '><foo:bar foo:baz='quux'/></root>"
+-        self.stream.parse(xml)
+-        self.assertEqual(self.elements[0].uri, " bar baz ")
+-        self.assertEqual(
+-            self.elements[0].attributes, {(" bar baz ", "baz"): "quux"})
+-
+-
+     def test_attributesWithNamespaces(self):
+         """
+         Attributes with namespace are parsed without Exception.

Reply to: