VYPR
High severityNVD Advisory· Published May 18, 2022· Updated Aug 3, 2024

Cross-site Scripting (XSS) - Generic in octoprint/octoprint

CVE-2022-1432

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.

PackageAffected versionsPatched versions
OctoPrintPyPI
< 1.8.01.8.0

Affected products

2
  • ghsa-coords
    Range: < 1.8.0
  • octoprint/octoprint/octoprintv5
    Range: unspecified

Patches

1
6d259d7e6f5b

🔒️ Fix XSS in webcam stream test

https://github.com/octoprint/octoprintGina HäußgeMay 11, 2022via ghsa
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

News mentions

0

No linked articles in our index yet.