Cross-site Scripting (XSS) - Generic in octoprint/octoprint
Description
Cross-site Scripting (XSS) - Generic in GitHub repository octoprint/octoprint prior to 1.8.0.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Stored XSS in OctoPrint's webcam stream test allows arbitrary JavaScript execution, fixed in 1.8.0.
Vulnerability
A stored cross-site scripting (XSS) vulnerability exists in OctoPrint's webcam stream test functionality. The webcam URL input field does not properly sanitize user-supplied data, allowing an authenticated attacker to inject arbitrary JavaScript. This affects OctoPrint versions prior to 1.8.0 [1][2].
Exploitation
An attacker must have access to the OctoPrint settings (e.g., as an authenticated user). By crafting a malicious webcam URL containing JavaScript payload, the script is stored and executed when another user views the settings page. No additional user interaction is required beyond viewing the page [2].
Impact
Successful exploitation leads to arbitrary JavaScript execution in the context of the OctoPrint interface. This can result in session hijacking, data exfiltration, or further attacks on the user's session [2].
Mitigation
The vulnerability is fixed in OctoPrint version 1.8.0, released on 2022-05-18. Users should upgrade to 1.8.0 or later. No workarounds are documented [1][2].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
OctoPrintPyPI | < 1.8.0 | 1.8.0 |
Affected products
2- octoprint/octoprint/octoprintv5Range: unspecified
Patches
16d259d7e6f5b🔒️ Fix XSS in webcam stream test
3 files changed · +80 −36
src/octoprint/static/js/app/helpers.js+63 −30 modified@@ -1542,41 +1542,74 @@ var copyToClipboard = function (text) { temp.remove(); }; -var determineWebcamStreamType = function (streamUrl) { - if (streamUrl) { - if (streamUrl.startsWith("webrtc")) { - return "webrtc"; - } +var getExternalHostUrl = function () { + var loc = window.location; + var port = ""; + if ( + (loc.protocol === "http:" && loc.port !== "80") || + (loc.protocol === "https:" && loc.port !== "443") + ) { + port = ":" + loc.port; + } + return loc.protocol + "//" + loc.hostname + port; +}; - var lastDotPosition = streamUrl.lastIndexOf("."); - var firstQuotationSignPosition = streamUrl.indexOf("?"); - if ( - lastDotPosition != -1 && - firstQuotationSignPosition != -1 && - lastDotPosition >= firstQuotationSignPosition - ) { - throw "Malformed URL. Cannot determine stream type."; - } +var validateWebcamUrl = function (streamUrl) { + if (!streamUrl) { + return false; + } - // If we have found a dot, try to extract the extension. - if (lastDotPosition > -1) { - if (firstQuotationSignPosition > -1) { - var extension = streamUrl.slice( - lastDotPosition + 1, - firstQuotationSignPosition - 1 - ); - } else { - var extension = streamUrl.slice(lastDotPosition + 1); - } - if (extension.toLowerCase() == "m3u8") { - return "hls"; - } - } - // By default, 'mjpg' is the stream type. - return "mjpg"; + var lower = streamUrl.toLowerCase(); + var toParse = streamUrl; + + if (lower.startsWith("//")) { + // protocol relative + toParse = window.location.protocol + streamUrl; + } else if (lower.startsWith("/")) { + // host relative + toParse = getExternalHostUrl() + streamUrl; + } else if ( + lower.startsWith("http:") || + lower.startsWith("https:") || + lower.startsWith("webrtc:") + ) { + // absolute & http/https/webrtc + toParse = streamUrl; } else { + return false; + } + + try { + return new URL(toParse); + } catch (e) { + return false; + } +}; + +var determineWebcamStreamType = function (streamUrl) { + if (!streamUrl) { throw "Empty streamUrl. Cannot determine stream type."; } + + var parsed = validateWebcamUrl(streamUrl); + if (!parsed) { + throw "Invalid streamUrl. Cannot determine stream type."; + } + + if (parsed.protocol === "webrtc:") { + return "webrtc"; + } + + var lastDotPosition = parsed.pathname.lastIndexOf("."); + if (lastDotPosition !== -1) { + var extension = parsed.pathname.substring(lastDotPosition + 1); + if (extension.toLowerCase() === "m3u8") { + return "hls"; + } + } + + // By default, 'mjpg' is the stream type. + return "mjpg"; }; var saveToLocalStorage = function (key, data) {
src/octoprint/static/js/app/viewmodels/settings.js+15 −4 modified@@ -319,6 +319,10 @@ $(function () { return ""; } }); + self.webcam_streamValid = ko.pureComputed(function () { + var url = self.webcam_streamUrl(); + return !url || validateWebcamUrl(url); + }); self.server_onlineCheckText = ko.observable(); self.server_onlineCheckOk = ko.observable(false); @@ -450,12 +454,19 @@ $(function () { var text = gettext( "If you see your webcam stream below, the entered stream URL is ok." ); - var streamType = self.webcam_streamType(); + + var streamType; + try { + streamType = self.webcam_streamType(); + } catch (e) { + streamType = ""; + } + var webcam_element; var webrtc_peer_connection; - if (streamType == "mjpg") { + if (streamType === "mjpg") { webcam_element = $('<img src="' + self.webcam_streamUrl() + '">'); - } else if (streamType == "hls") { + } else if (streamType === "hls") { webcam_element = $( '<video id="webcam_hls" muted autoplay style="width: 100%"/>' ); @@ -467,7 +478,7 @@ $(function () { hls.loadSource(self.webcam_streamUrl()); hls.attachMedia(video_element); } - } else if (isWebRTCAvailable() && streamType == "webrtc") { + } else if (isWebRTCAvailable() && streamType === "webrtc") { webcam_element = $( '<video id="webcam_webrtc" muted autoplay playsinline controls style="width: 100%"/>' );
src/octoprint/templates/snippets/settings/webcam/webcamStreamUrl.jinja2+2 −2 modified@@ -1,9 +1,9 @@ -<div class="control-group" title="{{ _('URL to embed into the UI for live viewing of the webcam stream')|edq }}"> +<div class="control-group" title="{{ _('URL to embed into the UI for live viewing of the webcam stream')|edq }}" data-bind="css: { error: !webcam_streamValid() }"> <label class="control-label" for="settings-webcamStreamUrl">{{ _('Stream URL') }}</label> <div class="controls"> <div class="input-append"> <input type="text" class="input-block-level" data-bind="value: webcam_streamUrl, valueUpdate: 'afterkeydown'" id="settings-webcamStreamUrl"> - <button class="btn" type="button" data-bind="click: testWebcamStreamUrl, enable: webcam_streamUrl() && !testWebcamStreamUrlBusy(), css: {disabled: !webcam_streamUrl() || testWebcamStreamUrlBusy()}"><i class="fas fa-spinner fa-spin" data-bind="visible: testWebcamStreamUrlBusy"></i> {{ _('Test') }}</button> + <button class="btn" type="button" data-bind="click: testWebcamStreamUrl, enable: !testWebcamStreamUrlBusy() && webcam_streamValid(), css: {disabled: testWebcamStreamUrlBusy() || !webcam_streamValid()}"><i class="fas fa-spinner fa-spin" data-bind="visible: testWebcamStreamUrlBusy"></i> {{ _('Test') }}</button> </div> <span class="help-block"> <p>{% trans %}Needs to be reachable from the browser displaying the OctoPrint UI, used to embed the webcam stream into the page.{% endtrans %}</p>
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-h8pc-j334-jjhmghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-1432ghsaADVISORY
- github.com/octoprint/octoprint/commit/6d259d7e6f5b0de9a1c762831537a386e53978d3ghsax_refsource_MISCWEB
- github.com/pypa/advisory-database/tree/main/vulns/octoprint/PYSEC-2022-201.yamlghsaWEB
- huntr.dev/bounties/cb545c63-a3c1-4d57-8f06-e4593ab389bfghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.