From e05b9b54d7a3535cecb783ee2548a7cfd925c20e Mon Sep 17 00:00:00 2001 From: Ryan Foster Date: Fri, 16 Oct 2026 17:11:53 -0400 Subject: [PATCH 1/6] audio: Escape possible HTML from log input --- checks/audio.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/checks/audio.py b/checks/audio.py index 1648291..0fe55bc 100644 --- a/checks/audio.py +++ b/checks/audio.py @@ -1,3 +1,4 @@ +import html import re from .vars import * @@ -38,7 +39,7 @@ def checkAudioBuffering(lines): if (len(lines) > index): m = audiobuf_re.search(lines[index + 1].replace('\r', '')) if m and m.group("source"): - append += "

Source affected (potential cause):" + m.group("source") + "" + append += "

Source affected (potential cause):" + html.escape(m.group("source")) + "" break return [LEVEL_INFO, "Max Audio Buffering", "Audio buffering hit the maximum value. This can be an indicator of very high system load and may affect stream latency or cause individual audio sources to stop working. Keep an eye on CPU usage especially, and close background programs if needed.

Occasionally, this can be caused by incorrect device timestamps. Restart OBS to reset buffering." + append] From a60f8ab8f846538c8e080fa517b6eaf0163c785b Mon Sep 17 00:00:00 2001 From: Ryan Foster Date: Fri, 16 Oct 2026 17:12:23 -0400 Subject: [PATCH 2/6] core: Escape possible HTML from log input --- checks/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checks/core.py b/checks/core.py index 1289893..7175db8 100644 --- a/checks/core.py +++ b/checks/core.py @@ -130,7 +130,7 @@ def checkSafeMode(lines): for line in modulesNotLoaded: found = re.search(r"'([^']+)'", line) if found: - moduleNames.append(found.group(1)) + moduleNames.append(html.escape(found.group(1))) if moduleNames: modulesNotLoadedString = "
\n" From acc1ecdb17efd6a2b85a4816c7f1f28ee99b022e Mon Sep 17 00:00:00 2001 From: Ryan Foster Date: Fri, 16 Oct 2026 17:12:53 -0400 Subject: [PATCH 3/6] fetchers: Escape possible HTML from log input --- checks/utils/fetchers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/checks/utils/fetchers.py b/checks/utils/fetchers.py index 5e86bae..ca3ad65 100644 --- a/checks/utils/fetchers.py +++ b/checks/utils/fetchers.py @@ -1,3 +1,4 @@ +import html import requests import re @@ -23,7 +24,7 @@ def getLinesGist(gistObject): def getDescriptionGist(gistObject): - desc = gistObject['description'] + desc = html.escape(gistObject['description']) if (desc == "" or desc is None): desc = gistObject['id'] return [0, "DESCRIPTION", desc] @@ -49,7 +50,7 @@ def getLinesHaste(hasteObject): def getDescription(lines): - return [0, "DESCRIPTION", lines[0]] + return [0, "DESCRIPTION", html.escape(lines[0])] # obsproject.com From aa40f1e0d028ea641e7693db291d9cceef31cf18 Mon Sep 17 00:00:00 2001 From: Ryan Foster Date: Fri, 16 Oct 2026 17:13:23 -0400 Subject: [PATCH 4/6] linux: Escape possible HTML from log input --- checks/linux.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/checks/linux.py b/checks/linux.py index fd8991c..4d68dfb 100644 --- a/checks/linux.py +++ b/checks/linux.py @@ -1,3 +1,5 @@ +import html + from .vars import * from .utils.utils import * @@ -42,7 +44,15 @@ def isX11(lines): return True +def isUbuntu2004(line): + if any(distro in line for distro in ['"Ubuntu" "20.04"', html.escape('"Ubuntu" "20.04"')]): + return True + else: + return False + + def checkDistro(lines): + """The distro log line is escaped before adding to the returned list. Take care when comparing against this value.""" isDistroNix = search('Distribution:', lines) if len(isDistroNix) <= 0: @@ -59,7 +69,7 @@ def checkDistro(lines): distroHelp = 'No distribution detected. This can lead to undefined behavior. Please consult your distribution\'s support channels on how to fix this.' return [LEVEL_WARNING, distro, distroHelp] - return [LEVEL_INFO, distro, distroHelp] + return [LEVEL_INFO, html.escape(distro), distroHelp] def checkFlatpak(lines): @@ -90,7 +100,7 @@ def checkWayland(lines): return isDistroNix = checkDistro(lines) - if isDistroNix and ('"Ubuntu" "20.04"' in isDistroNix[1]): + if isDistroNix and isUbuntu2004(isDistroNix[1]): return [LEVEL_CRITICAL, "Ubuntu 20.04 under Wayland", "Ubuntu 20.04 does not provide the needed dependencies for OBS to capture under Wayland.
Capture will only function under X11/Xorg."] @@ -148,7 +158,7 @@ def checkDesktopEnvironment(lines): desktopEnvironment = '' if (len(desktopEnvironment) > 0): - return [LEVEL_INFO, desktopEnvironment, ''] + return [LEVEL_INFO, html.escape(desktopEnvironment), ''] def checkMissingModules(lines): From bc8b61123b2eb513aa817bce618d21dc57a0e507 Mon Sep 17 00:00:00 2001 From: Ryan Foster Date: Fri, 16 Oct 2026 17:13:54 -0400 Subject: [PATCH 5/6] plugins: Escape possible HTML from log input --- checks/plugins.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/checks/plugins.py b/checks/plugins.py index d91cd16..c494dd7 100644 --- a/checks/plugins.py +++ b/checks/plugins.py @@ -1,3 +1,5 @@ +import html + from .vars import * from .utils.utils import * from .core import * @@ -14,7 +16,7 @@ def checkImports(lines): if plugin: notLoadedPlugins.append(plugin) - pluginString = "
  • ".join(str(plugin) for plugin in notLoadedPlugins) + pluginString = "
  • ".join(html.escape(plugin) for plugin in notLoadedPlugins) if notLoadedPlugins: pluginString = f"

    Plugins affected:
    • {pluginString}
    " return [LEVEL_WARNING, f"Plugins Not Loaded ({len(notLoadedPlugins)})", @@ -52,7 +54,7 @@ def checkPluginList(lines): else: thirdPartyPlugins = [] - pluginString = "
  • ".join(str(plugin) for plugin in thirdPartyPlugins) + pluginString = "
  • ".join(html.escape(plugin) for plugin in thirdPartyPlugins) if thirdPartyPlugins: return [LEVEL_INFO, f"Third-Party Plugins ({len(thirdPartyPlugins)})", From 5a21bef2c38f8015bd731ca48d4a808acee532a3 Mon Sep 17 00:00:00 2001 From: Ryan Foster Date: Fri, 16 Oct 2026 17:14:24 -0400 Subject: [PATCH 6/6] windows: Escape possible HTML from log input --- checks/windows.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/checks/windows.py b/checks/windows.py index 3ca46f5..c14b243 100644 --- a/checks/windows.py +++ b/checks/windows.py @@ -104,7 +104,7 @@ def checkRefreshes(lines): if len(r) > 1: rfrshs = "
    " for output, hz in refreshes.items(): - rfrshs += "
    " + output + ": " + str(int(hz)) + "Hz" + rfrshs += "
    " + html.escape(output) + ": " + str(int(hz)) + "Hz" return [LEVEL_WARNING, "Mismatched Refresh Rates", "The version of Windows you are running has a limitation which causes performance issues in hardware accelerated applications (such as games) if multiple monitors with different refresh rates are present. Your system's monitors have " + str(len(r)) + """ different refresh rates, so you are affected by this limitation.

    To fix this issue, we recommend updating to the Windows 10 May 2020 Update. Follow these instructions if you're not sure how to update.""" + rfrshs] return @@ -174,7 +174,7 @@ def checkWasapiSamples(lines): if obsSample != "": smpls += "
    OBS Sample Rate: " + str(obsSample) + " Hz" for d, hz in samples.items(): - smpls += "
    " + d + ": " + str(hz) + " Hz" + smpls += "
    " + html.escape(d) + ": " + str(hz) + " Hz" return [LEVEL_WARNING, "Mismatched Sample Rates", "At least one of your audio devices has a sample rate that doesn't match the rest. This can result in audio drift over time or sound distortion. Check your audio devices in Windows settings (both Playback and Recording) and ensure the Default Format (under Advanced) is consistent. 48000 Hz is recommended." + smpls] @@ -350,7 +350,7 @@ def checkGCAdmin(lines): error_lines = search("could not open process:", lines) gc_error_lines = search("game-capture", error_lines) # Makes sure the error comes from a game capture, in case. if gc_error_lines: - exe_list = set([gc_admin_re.search(line).group("executable").strip() for line in gc_error_lines]) + exe_list = set([html.escape(gc_admin_re.search(line).group("executable").strip()) for line in gc_error_lines]) return [LEVEL_INFO, "Game Capture Permissions", "Game Capture was unable to capture the following applications. This may be solved by running OBS as an Administrator:
    \n
      \n
    • " + "
    • \n
    • ".join(exe_list)