Incorrect Provision of Specified Functionality in qutebrowser
Description
In qutebrowser versions less than 1.11.1, reloading a page with certificate errors shows a green URL. After a certificate error was overridden by the user, qutebrowser displays the URL as yellow (colors.statusbar.url.warn.fg). However, when the affected website was subsequently loaded again, the URL was mistakenly displayed as green (colors.statusbar.url.success_https). While the user already has seen a certificate error prompt at this point (or set content.ssl_strict to false, which is not recommended), this could still provide a false sense of security. This has been fixed in 1.11.1 and 1.12.0. All versions of qutebrowser are believed to be affected, though versions before v0.11.x couldn't be tested. Backported patches for older versions (greater than or equal to 1.4.0 and less than or equal to 1.10.2) are available, but no further releases are planned.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
qutebrowserPyPI | < 1.11.1 | 1.11.1 |
Affected products
1- Range: < 1.11.1
Patches
101b7946ed14b3Update changelog
1 file changed · +10 −1
doc/changelog.asciidoc+10 −1 modified@@ -45,7 +45,16 @@ Fixed v1.11.1 (unreleased) -------------------- -No changes yet. +Security +~~~~~~~~ + +- After a certificate error was overridden by the user, qutebrowser displays + the URL as yellow (`colors.statusbar.url.warn.fg`). However, when the + affected website was subsequently loaded again, the URL was mistakenly + displayed as green (`colors.statusbar.url.success_https`). While the user + already has seen a certificate error prompt at this point (or set + `content.ssl_strict` to `false` which is not recommended), this could still + provide a false sense of security. This is now fixed. v1.11.0 (2020-04-27) --------------------
d28ed758d077Security: Remember hosts with ignored cert errors for load status
3 files changed · +17 −9
qutebrowser/browser/browsertab.py+12 −4 modified@@ -867,6 +867,13 @@ class AbstractTab(QWidget): # arg 1: The exit code. renderer_process_terminated = pyqtSignal(TerminationStatus, int) + # Hosts for which a certificate error happened. Shared between all tabs. + # + # Note that we remember hosts here, without scheme/port: + # QtWebEngine/Chromium also only remembers hostnames, and certificates are + # for a given hostname anyways. + _insecure_hosts = set() # type: typing.Set[str] + def __init__(self, *, win_id: int, private: bool, parent: QWidget = None) -> None: self.is_private = private @@ -884,7 +891,6 @@ def __init__(self, *, win_id: int, private: bool, self._layout = miscwidgets.WrapperLayout(self) self._widget = typing.cast(QWidget, None) self._progress = 0 - self._has_ssl_errors = False self._load_status = usertypes.LoadStatus.none self._tab_event_filter = eventfilter.TabEventFilter( self, parent=self) @@ -971,7 +977,6 @@ def _on_url_changed(self, url: QUrl) -> None: @pyqtSlot() def _on_load_started(self) -> None: self._progress = 0 - self._has_ssl_errors = False self.data.viewing_source = False self._set_load_status(usertypes.LoadStatus.loading) self.load_started.emit() @@ -1030,9 +1035,12 @@ def _update_load_status(self, ok: bool) -> None: Needs to be called by subclasses to trigger a load status update, e.g. as a response to a loadFinished signal. """ - if ok and not self._has_ssl_errors: + if ok: if self.url().scheme() == 'https': - self._set_load_status(usertypes.LoadStatus.success_https) + if self.url().host() in self._insecure_hosts: + self._set_load_status(usertypes.LoadStatus.warn) + else: + self._set_load_status(usertypes.LoadStatus.success_https) else: self._set_load_status(usertypes.LoadStatus.success) elif ok:
qutebrowser/browser/webengine/webenginetab.py+2 −2 modified@@ -1450,9 +1450,9 @@ def _on_load_finished(self, ok: bool) -> None: @pyqtSlot(certificateerror.CertificateErrorWrapper) def _on_ssl_errors(self, error): - self._has_ssl_errors = True - url = error.url() + self._insecure_hosts.add(url.host()) + log.webview.debug("Certificate error: {}".format(error)) if error.is_overridable():
qutebrowser/browser/webkit/webkittab.py+3 −3 modified@@ -847,9 +847,9 @@ def _on_navigation_request(self, navigation): if navigation.is_main_frame: self.settings.update_for_url(navigation.url) - @pyqtSlot() - def _on_ssl_errors(self): - self._has_ssl_errors = True + @pyqtSlot('QNetworkReply*') + def _on_ssl_errors(self, reply): + self._insecure_hosts.add(reply.url().host()) def _connect_signals(self): view = self._widget
9bd1cf585fccSecurity: Remember hosts with ignored cert errors for load status
3 files changed · +17 −9
qutebrowser/browser/browsertab.py+12 −4 modified@@ -867,6 +867,13 @@ class AbstractTab(QWidget): # arg 1: The exit code. renderer_process_terminated = pyqtSignal(TerminationStatus, int) + # Hosts for which a certificate error happened. Shared between all tabs. + # + # Note that we remember hosts here, without scheme/port: + # QtWebEngine/Chromium also only remembers hostnames, and certificates are + # for a given hostname anyways. + _insecure_hosts = set() # type: typing.Set[str] + def __init__(self, *, win_id: int, private: bool, parent: QWidget = None) -> None: self.is_private = private @@ -884,7 +891,6 @@ def __init__(self, *, win_id: int, private: bool, self._layout = miscwidgets.WrapperLayout(self) self._widget = typing.cast(QWidget, None) self._progress = 0 - self._has_ssl_errors = False self._load_status = usertypes.LoadStatus.none self._tab_event_filter = eventfilter.TabEventFilter( self, parent=self) @@ -971,7 +977,6 @@ def _on_url_changed(self, url: QUrl) -> None: @pyqtSlot() def _on_load_started(self) -> None: self._progress = 0 - self._has_ssl_errors = False self.data.viewing_source = False self._set_load_status(usertypes.LoadStatus.loading) self.load_started.emit() @@ -1030,9 +1035,12 @@ def _update_load_status(self, ok: bool) -> None: Needs to be called by subclasses to trigger a load status update, e.g. as a response to a loadFinished signal. """ - if ok and not self._has_ssl_errors: + if ok: if self.url().scheme() == 'https': - self._set_load_status(usertypes.LoadStatus.success_https) + if self.url().host() in self._insecure_hosts: + self._set_load_status(usertypes.LoadStatus.warn) + else: + self._set_load_status(usertypes.LoadStatus.success_https) else: self._set_load_status(usertypes.LoadStatus.success) elif ok:
qutebrowser/browser/webengine/webenginetab.py+2 −2 modified@@ -1428,9 +1428,9 @@ def _on_load_finished(self, ok: bool) -> None: @pyqtSlot(certificateerror.CertificateErrorWrapper) def _on_ssl_errors(self, error): - self._has_ssl_errors = True - url = error.url() + self._insecure_hosts.add(url.host()) + log.webview.debug("Certificate error: {}".format(error)) if error.is_overridable():
qutebrowser/browser/webkit/webkittab.py+3 −3 modified@@ -847,9 +847,9 @@ def _on_navigation_request(self, navigation): if navigation.is_main_frame: self.settings.update_for_url(navigation.url) - @pyqtSlot() - def _on_ssl_errors(self): - self._has_ssl_errors = True + @pyqtSlot('QNetworkReply*') + def _on_ssl_errors(self, reply): + self._insecure_hosts.add(reply.url().host()) def _connect_signals(self): view = self._widget
f5d801251aa5Security: Remember hosts with ignored cert errors for load status
3 files changed · +17 −9
qutebrowser/browser/browsertab.py+12 −4 modified@@ -860,6 +860,13 @@ class AbstractTab(QWidget): # arg 1: The exit code. renderer_process_terminated = pyqtSignal(TerminationStatus, int) + # Hosts for which a certificate error happened. Shared between all tabs. + # + # Note that we remember hosts here, without scheme/port: + # QtWebEngine/Chromium also only remembers hostnames, and certificates are + # for a given hostname anyways. + _insecure_hosts = set() # type: typing.Set[str] + def __init__(self, *, win_id: int, private: bool, parent: QWidget = None) -> None: self.is_private = private @@ -877,7 +884,6 @@ def __init__(self, *, win_id: int, private: bool, self._layout = miscwidgets.WrapperLayout(self) self._widget = None # type: typing.Optional[QWidget] self._progress = 0 - self._has_ssl_errors = False self._load_status = usertypes.LoadStatus.none self._mouse_event_filter = mouse.MouseEventFilter( self, parent=self) @@ -965,7 +971,6 @@ def _on_url_changed(self, url: QUrl) -> None: @pyqtSlot() def _on_load_started(self) -> None: self._progress = 0 - self._has_ssl_errors = False self.data.viewing_source = False self._set_load_status(usertypes.LoadStatus.loading) self.load_started.emit() @@ -1013,9 +1018,12 @@ def _on_load_finished(self, ok: bool) -> None: sess_manager.save_autosave() - if ok and not self._has_ssl_errors: + if ok: if self.url().scheme() == 'https': - self._set_load_status(usertypes.LoadStatus.success_https) + if self.url().host() in self._insecure_hosts: + self._set_load_status(usertypes.LoadStatus.warn) + else: + self._set_load_status(usertypes.LoadStatus.success_https) else: self._set_load_status(usertypes.LoadStatus.success) elif ok:
qutebrowser/browser/webengine/webenginetab.py+2 −2 modified@@ -1385,9 +1385,9 @@ def _on_load_finished(self, ok): @pyqtSlot(certificateerror.CertificateErrorWrapper) def _on_ssl_errors(self, error): - self._has_ssl_errors = True - url = error.url() + self._insecure_hosts.add(url.host()) + log.webview.debug("Certificate error: {}".format(error)) if error.is_overridable():
qutebrowser/browser/webkit/webkittab.py+3 −3 modified@@ -837,9 +837,9 @@ def _on_navigation_request(self, navigation): if navigation.is_main_frame: self.settings.update_for_url(navigation.url) - @pyqtSlot() - def _on_ssl_errors(self): - self._has_ssl_errors = True + @pyqtSlot('QNetworkReply*') + def _on_ssl_errors(self, reply): + self._insecure_hosts.add(reply.url().host()) def _connect_signals(self): view = self._widget
6821c236f9aeSecurity: Remember hosts with ignored cert errors for load status
3 files changed · +17 −9
qutebrowser/browser/browsertab.py+12 −4 modified@@ -869,6 +869,13 @@ class AbstractTab(QWidget): # arg 1: The exit code. renderer_process_terminated = pyqtSignal(TerminationStatus, int) + # Hosts for which a certificate error happened. Shared between all tabs. + # + # Note that we remember hosts here, without scheme/port: + # QtWebEngine/Chromium also only remembers hostnames, and certificates are + # for a given hostname anyways. + _insecure_hosts = set() # type: typing.Set[str] + def __init__(self, *, win_id: int, private: bool, parent: QWidget = None) -> None: self.is_private = private @@ -886,7 +893,6 @@ def __init__(self, *, win_id: int, private: bool, self._layout = miscwidgets.WrapperLayout(self) self._widget = typing.cast(QWidget, None) self._progress = 0 - self._has_ssl_errors = False self._load_status = usertypes.LoadStatus.none self._tab_event_filter = eventfilter.TabEventFilter( self, parent=self) @@ -973,7 +979,6 @@ def _on_url_changed(self, url: QUrl) -> None: @pyqtSlot() def _on_load_started(self) -> None: self._progress = 0 - self._has_ssl_errors = False self.data.viewing_source = False self._set_load_status(usertypes.LoadStatus.loading) self.load_started.emit() @@ -1032,9 +1037,12 @@ def _update_load_status(self, ok: bool) -> None: Needs to be called by subclasses to trigger a load status update, e.g. as a response to a loadFinished signal. """ - if ok and not self._has_ssl_errors: + if ok: if self.url().scheme() == 'https': - self._set_load_status(usertypes.LoadStatus.success_https) + if self.url().host() in self._insecure_hosts: + self._set_load_status(usertypes.LoadStatus.warn) + else: + self._set_load_status(usertypes.LoadStatus.success_https) else: self._set_load_status(usertypes.LoadStatus.success) elif ok:
qutebrowser/browser/webengine/webenginetab.py+2 −2 modified@@ -1549,9 +1549,9 @@ def _on_load_finished(self, ok: bool) -> None: @pyqtSlot(certificateerror.CertificateErrorWrapper) def _on_ssl_errors(self, error): - self._has_ssl_errors = True - url = error.url() + self._insecure_hosts.add(url.host()) + log.webview.debug("Certificate error: {}".format(error)) if error.is_overridable():
qutebrowser/browser/webkit/webkittab.py+3 −3 modified@@ -849,9 +849,9 @@ def _on_navigation_request(self, navigation): if navigation.is_main_frame: self.settings.update_for_url(navigation.url) - @pyqtSlot() - def _on_ssl_errors(self): - self._has_ssl_errors = True + @pyqtSlot('QNetworkReply*') + def _on_ssl_errors(self, reply): + self._insecure_hosts.add(reply.url().host()) def _connect_signals(self): view = self._widget
a45ca9c788f6Security: Remember hosts with ignored cert errors for load status
3 files changed · +17 −9
qutebrowser/browser/browsertab.py+12 −4 modified@@ -755,6 +755,13 @@ class AbstractTab(QWidget): renderer_process_terminated = pyqtSignal(TerminationStatus, int) predicted_navigation = pyqtSignal(QUrl) + # Hosts for which a certificate error happened. Shared between all tabs. + # + # Note that we remember hosts here, without scheme/port: + # QtWebEngine/Chromium also only remembers hostnames, and certificates are + # for a given hostname anyways. + _insecure_hosts = set() # type: typing.Set[str] + def __init__(self, *, win_id, mode_manager, private, parent=None): self.private = private self.win_id = win_id @@ -771,7 +778,6 @@ def __init__(self, *, win_id, mode_manager, private, parent=None): self._layout = miscwidgets.WrapperLayout(self) self._widget = None self._progress = 0 - self._has_ssl_errors = False self._mode_manager = mode_manager self._load_status = usertypes.LoadStatus.none self._mouse_event_filter = mouse.MouseEventFilter( @@ -858,7 +864,6 @@ def _on_url_changed(self, url): @pyqtSlot() def _on_load_started(self): self._progress = 0 - self._has_ssl_errors = False self.data.viewing_source = False self._set_load_status(usertypes.LoadStatus.loading) self.load_started.emit() @@ -917,9 +922,12 @@ def _on_load_finished(self, ok): sess_manager = objreg.get('session-manager') sess_manager.save_autosave() - if ok and not self._has_ssl_errors: + if ok: if self.url().scheme() == 'https': - self._set_load_status(usertypes.LoadStatus.success_https) + if self.url().host() in self._insecure_hosts: + self._set_load_status(usertypes.LoadStatus.warn) + else: + self._set_load_status(usertypes.LoadStatus.success_https) else: self._set_load_status(usertypes.LoadStatus.success) elif ok:
qutebrowser/browser/webengine/webenginetab.py+2 −2 modified@@ -1336,9 +1336,9 @@ def _on_load_finished(self, ok): @pyqtSlot(certificateerror.CertificateErrorWrapper) def _on_ssl_errors(self, error): - self._has_ssl_errors = True - url = error.url() + self._insecure_hosts.add(url.host()) + log.webview.debug("Certificate error: {}".format(error)) if error.is_overridable():
qutebrowser/browser/webkit/webkittab.py+3 −3 modified@@ -818,9 +818,9 @@ def _on_navigation_request(self, navigation): if navigation.is_main_frame: self.settings.update_for_url(navigation.url) - @pyqtSlot() - def _on_ssl_errors(self): - self._has_ssl_errors = True + @pyqtSlot('QNetworkReply*') + def _on_ssl_errors(self, reply): + self._insecure_hosts.add(reply.url().host()) def _connect_signals(self): view = self._widget
2281a205c3e7Security: Remember hosts with ignored cert errors for load status
3 files changed · +17 −9
qutebrowser/browser/browsertab.py+12 −4 modified@@ -866,6 +866,13 @@ class AbstractTab(QWidget): # arg 1: The exit code. renderer_process_terminated = pyqtSignal(TerminationStatus, int) + # Hosts for which a certificate error happened. Shared between all tabs. + # + # Note that we remember hosts here, without scheme/port: + # QtWebEngine/Chromium also only remembers hostnames, and certificates are + # for a given hostname anyways. + _insecure_hosts = set() # type: typing.Set[str] + def __init__(self, *, win_id: int, private: bool, parent: QWidget = None) -> None: self.is_private = private @@ -883,7 +890,6 @@ def __init__(self, *, win_id: int, private: bool, self._layout = miscwidgets.WrapperLayout(self) self._widget = None # type: typing.Optional[QWidget] self._progress = 0 - self._has_ssl_errors = False self._load_status = usertypes.LoadStatus.none self._tab_event_filter = eventfilter.TabEventFilter( self, parent=self) @@ -971,7 +977,6 @@ def _on_url_changed(self, url: QUrl) -> None: @pyqtSlot() def _on_load_started(self) -> None: self._progress = 0 - self._has_ssl_errors = False self.data.viewing_source = False self._set_load_status(usertypes.LoadStatus.loading) self.load_started.emit() @@ -1031,9 +1036,12 @@ def _update_load_status(self, ok: bool) -> None: Needs to be called by subclasses to trigger a load status update, e.g. as a response to a loadFinished signal. """ - if ok and not self._has_ssl_errors: + if ok: if self.url().scheme() == 'https': - self._set_load_status(usertypes.LoadStatus.success_https) + if self.url().host() in self._insecure_hosts: + self._set_load_status(usertypes.LoadStatus.warn) + else: + self._set_load_status(usertypes.LoadStatus.success_https) else: self._set_load_status(usertypes.LoadStatus.success) elif ok:
qutebrowser/browser/webengine/webenginetab.py+2 −2 modified@@ -1423,9 +1423,9 @@ def _on_load_finished(self, ok: bool) -> None: @pyqtSlot(certificateerror.CertificateErrorWrapper) def _on_ssl_errors(self, error): - self._has_ssl_errors = True - url = error.url() + self._insecure_hosts.add(url.host()) + log.webview.debug("Certificate error: {}".format(error)) if error.is_overridable():
qutebrowser/browser/webkit/webkittab.py+3 −3 modified@@ -851,9 +851,9 @@ def _on_navigation_request(self, navigation): if navigation.is_main_frame: self.settings.update_for_url(navigation.url) - @pyqtSlot() - def _on_ssl_errors(self): - self._has_ssl_errors = True + @pyqtSlot('QNetworkReply*') + def _on_ssl_errors(self, reply): + self._insecure_hosts.add(reply.url().host()) def _connect_signals(self): view = self._widget
19f01bb42d02Security: Remember hosts with ignored cert errors for load status
3 files changed · +17 −9
qutebrowser/browser/browsertab.py+12 −4 modified@@ -864,6 +864,13 @@ class AbstractTab(QWidget): # arg 1: The exit code. renderer_process_terminated = pyqtSignal(TerminationStatus, int) + # Hosts for which a certificate error happened. Shared between all tabs. + # + # Note that we remember hosts here, without scheme/port: + # QtWebEngine/Chromium also only remembers hostnames, and certificates are + # for a given hostname anyways. + _insecure_hosts = set() # type: typing.Set[str] + def __init__(self, *, win_id: int, private: bool, parent: QWidget = None) -> None: self.is_private = private @@ -881,7 +888,6 @@ def __init__(self, *, win_id: int, private: bool, self._layout = miscwidgets.WrapperLayout(self) self._widget = None # type: typing.Optional[QWidget] self._progress = 0 - self._has_ssl_errors = False self._load_status = usertypes.LoadStatus.none self._mouse_event_filter = mouse.MouseEventFilter( self, parent=self) @@ -969,7 +975,6 @@ def _on_url_changed(self, url: QUrl) -> None: @pyqtSlot() def _on_load_started(self) -> None: self._progress = 0 - self._has_ssl_errors = False self.data.viewing_source = False self._set_load_status(usertypes.LoadStatus.loading) self.load_started.emit() @@ -1029,9 +1034,12 @@ def _update_load_status(self, ok: bool) -> None: Needs to be called by subclasses to trigger a load status update, e.g. as a response to a loadFinished signal. """ - if ok and not self._has_ssl_errors: + if ok: if self.url().scheme() == 'https': - self._set_load_status(usertypes.LoadStatus.success_https) + if self.url().host() in self._insecure_hosts: + self._set_load_status(usertypes.LoadStatus.warn) + else: + self._set_load_status(usertypes.LoadStatus.success_https) else: self._set_load_status(usertypes.LoadStatus.success) elif ok:
qutebrowser/browser/webengine/webenginetab.py+2 −2 modified@@ -1395,9 +1395,9 @@ def _on_load_finished(self, ok: bool) -> None: @pyqtSlot(certificateerror.CertificateErrorWrapper) def _on_ssl_errors(self, error): - self._has_ssl_errors = True - url = error.url() + self._insecure_hosts.add(url.host()) + log.webview.debug("Certificate error: {}".format(error)) if error.is_overridable():
qutebrowser/browser/webkit/webkittab.py+3 −3 modified@@ -851,9 +851,9 @@ def _on_navigation_request(self, navigation): if navigation.is_main_frame: self.settings.update_for_url(navigation.url) - @pyqtSlot() - def _on_ssl_errors(self): - self._has_ssl_errors = True + @pyqtSlot('QNetworkReply*') + def _on_ssl_errors(self, reply): + self._insecure_hosts.add(reply.url().host()) def _connect_signals(self): view = self._widget
4020210b193fSecurity: Remember hosts with ignored cert errors for load status
3 files changed · +17 −9
qutebrowser/browser/browsertab.py+12 −4 modified@@ -737,6 +737,13 @@ class AbstractTab(QWidget): renderer_process_terminated = pyqtSignal(TerminationStatus, int) predicted_navigation = pyqtSignal(QUrl) + # Hosts for which a certificate error happened. Shared between all tabs. + # + # Note that we remember hosts here, without scheme/port: + # QtWebEngine/Chromium also only remembers hostnames, and certificates are + # for a given hostname anyways. + _insecure_hosts = set() # type: typing.Set[str] + def __init__(self, *, win_id, mode_manager, private, parent=None): self.private = private self.win_id = win_id @@ -753,7 +760,6 @@ def __init__(self, *, win_id, mode_manager, private, parent=None): self._layout = miscwidgets.WrapperLayout(self) self._widget = None self._progress = 0 - self._has_ssl_errors = False self._mode_manager = mode_manager self._load_status = usertypes.LoadStatus.none self._mouse_event_filter = mouse.MouseEventFilter( @@ -840,7 +846,6 @@ def _on_url_changed(self, url): @pyqtSlot() def _on_load_started(self): self._progress = 0 - self._has_ssl_errors = False self.data.viewing_source = False self._set_load_status(usertypes.LoadStatus.loading) self.load_started.emit() @@ -899,9 +904,12 @@ def _on_load_finished(self, ok): sess_manager = objreg.get('session-manager') sess_manager.save_autosave() - if ok and not self._has_ssl_errors: + if ok: if self.url().scheme() == 'https': - self._set_load_status(usertypes.LoadStatus.success_https) + if self.url().host() in self._insecure_hosts: + self._set_load_status(usertypes.LoadStatus.warn) + else: + self._set_load_status(usertypes.LoadStatus.success_https) else: self._set_load_status(usertypes.LoadStatus.success) elif ok:
qutebrowser/browser/webengine/webenginetab.py+2 −2 modified@@ -1278,9 +1278,9 @@ def _on_load_finished(self, ok): @pyqtSlot(certificateerror.CertificateErrorWrapper) def _on_ssl_errors(self, error): - self._has_ssl_errors = True - url = error.url() + self._insecure_hosts.add(url.host()) + log.webview.debug("Certificate error: {}".format(error)) if error.is_overridable():
qutebrowser/browser/webkit/webkittab.py+3 −3 modified@@ -808,9 +808,9 @@ def _on_navigation_request(self, navigation): if navigation.is_main_frame: self.settings.update_for_url(navigation.url) - @pyqtSlot() - def _on_ssl_errors(self): - self._has_ssl_errors = True + @pyqtSlot('QNetworkReply*') + def _on_ssl_errors(self, reply): + self._insecure_hosts.add(reply.url().host()) def _connect_signals(self): view = self._widget
021ab572a319Security: Remember hosts with ignored cert errors for load status
3 files changed · +17 −9
qutebrowser/browser/browsertab.py+12 −4 modified@@ -869,6 +869,13 @@ class AbstractTab(QWidget): # arg 1: The exit code. renderer_process_terminated = pyqtSignal(TerminationStatus, int) + # Hosts for which a certificate error happened. Shared between all tabs. + # + # Note that we remember hosts here, without scheme/port: + # QtWebEngine/Chromium also only remembers hostnames, and certificates are + # for a given hostname anyways. + _insecure_hosts = set() # type: typing.Set[str] + def __init__(self, *, win_id: int, private: bool, parent: QWidget = None) -> None: self.is_private = private @@ -886,7 +893,6 @@ def __init__(self, *, win_id: int, private: bool, self._layout = miscwidgets.WrapperLayout(self) self._widget = typing.cast(QWidget, None) self._progress = 0 - self._has_ssl_errors = False self._load_status = usertypes.LoadStatus.none self._tab_event_filter = eventfilter.TabEventFilter( self, parent=self) @@ -973,7 +979,6 @@ def _on_url_changed(self, url: QUrl) -> None: @pyqtSlot() def _on_load_started(self) -> None: self._progress = 0 - self._has_ssl_errors = False self.data.viewing_source = False self._set_load_status(usertypes.LoadStatus.loading) self.load_started.emit() @@ -1032,9 +1037,12 @@ def _update_load_status(self, ok: bool) -> None: Needs to be called by subclasses to trigger a load status update, e.g. as a response to a loadFinished signal. """ - if ok and not self._has_ssl_errors: + if ok: if self.url().scheme() == 'https': - self._set_load_status(usertypes.LoadStatus.success_https) + if self.url().host() in self._insecure_hosts: + self._set_load_status(usertypes.LoadStatus.warn) + else: + self._set_load_status(usertypes.LoadStatus.success_https) else: self._set_load_status(usertypes.LoadStatus.success) elif ok:
qutebrowser/browser/webengine/webenginetab.py+2 −2 modified@@ -1549,9 +1549,9 @@ def _on_load_finished(self, ok: bool) -> None: @pyqtSlot(certificateerror.CertificateErrorWrapper) def _on_ssl_errors(self, error): - self._has_ssl_errors = True - url = error.url() + self._insecure_hosts.add(url.host()) + log.webview.debug("Certificate error: {}".format(error)) if error.is_overridable():
qutebrowser/browser/webkit/webkittab.py+3 −3 modified@@ -849,9 +849,9 @@ def _on_navigation_request(self, navigation): if navigation.is_main_frame: self.settings.update_for_url(navigation.url) - @pyqtSlot() - def _on_ssl_errors(self): - self._has_ssl_errors = True + @pyqtSlot('QNetworkReply*') + def _on_ssl_errors(self, reply): + self._insecure_hosts.add(reply.url().host()) def _connect_signals(self): view = self._widget
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
21- github.com/advisories/GHSA-4rcq-jv2f-898jghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/7YWJ5QNHXKTGG5NLV7EGEOKPBVZBA5GS/mitrevendor-advisoryx_refsource_FEDORA
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/MKAZOOTJ2MBHTYVYQQ52NL53F5CB2XAP/mitrevendor-advisoryx_refsource_FEDORA
- nvd.nist.gov/vuln/detail/CVE-2020-11054ghsaADVISORY
- bugs.kde.org/show_bug.cgimitrex_refsource_MISC
- github.com/pypa/advisory-database/tree/main/vulns/qutebrowser/PYSEC-2020-97.yamlghsaWEB
- github.com/qutebrowser/qutebrowser/commit/021ab572a319ca3db5907a33a59774f502b3b975ghsax_refsource_MISCWEB
- github.com/qutebrowser/qutebrowser/commit/19f01bb42d02da539446a52a25bb0c1232b86327ghsax_refsource_MISCWEB
- github.com/qutebrowser/qutebrowser/commit/1b7946ed14b386a24db050f2d6dba81ba6518755ghsax_refsource_MISCWEB
- github.com/qutebrowser/qutebrowser/commit/2281a205c3e70ec20f35ec8fafecee0d5c4f3478ghsax_refsource_MISCWEB
- github.com/qutebrowser/qutebrowser/commit/4020210b193f77cf1785b21717f6ef7c5de5f0f8ghsax_refsource_MISCWEB
- github.com/qutebrowser/qutebrowser/commit/6821c236f9ae23adf21d46ce0d56768ac8d0c467ghsax_refsource_MISCWEB
- github.com/qutebrowser/qutebrowser/commit/9bd1cf585fccdfe8318fff7af793730e74a04db3ghsax_refsource_MISCWEB
- github.com/qutebrowser/qutebrowser/commit/a45ca9c788f648d10cccce2af41405bf25ee2948ghsax_refsource_MISCWEB
- github.com/qutebrowser/qutebrowser/commit/d28ed758d077a5bf19ddac4da468f7224114df23ghsax_refsource_MISCWEB
- github.com/qutebrowser/qutebrowser/commit/f5d801251aa5436aff44660c87d7013e29ac5864ghsax_refsource_MISCWEB
- github.com/qutebrowser/qutebrowser/issues/5403ghsax_refsource_MISCWEB
- github.com/qutebrowser/qutebrowser/security/advisories/GHSA-4rcq-jv2f-898jghsax_refsource_CONFIRMWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/7YWJ5QNHXKTGG5NLV7EGEOKPBVZBA5GSghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/MKAZOOTJ2MBHTYVYQQ52NL53F5CB2XAPghsaWEB
- tracker.die-offenbachs.homelinux.org/eric/issue328ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.