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

Bug#1010613: marked as done (bullseye-pu: package twisted/20.3.0-7+deb11u1)



Your message dated Sat, 09 Jul 2022 11:47:43 +0100
with message-id <2280fe8c78e64b02a6c1d04c6dde5a32e342ba81.camel@adam-barratt.org.uk>
and subject line Closing requests for updates included in 11.4
has caused the Debian Bug report #1010613,
regarding bullseye-pu: package twisted/20.3.0-7+deb11u1
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
1010613: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1010613
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
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.

--- End Message ---
--- Begin Message ---
Package: release.debian.org
Version: 11.4

(re-sending with fixed bug numbers)

Hi,

The updates discussed in these bugs were included in today's bullseye
point release.

Regards,

Adam

--- End Message ---

Reply to: