diff --git a/common/lib/xmodule/xmodule/js/src/capa/schematic.js b/common/lib/xmodule/xmodule/js/src/capa/schematic.js
index 63edb278d87381868666d5039c875a9e047dadd2..92c274c7bce219d99ef648092fe5bc0efc5048c5 100644
--- a/common/lib/xmodule/xmodule/js/src/capa/schematic.js
+++ b/common/lib/xmodule/xmodule/js/src/capa/schematic.js
@@ -2810,7 +2810,7 @@ schematic = (function() {
 			    // for each requested freq, interpolate response value
 			    for (var k = 1; k < flist.length; k++) {
 				var f = flist[k];
-				var v = interpolate(f,x_values,values);  //xss-lint: disable=javascript-interpolate
+				var v = interpolate(f,x_values,values);
 				// convert to dB
 				fvlist.push([f,v == undefined ? 'undefined' : 20.0 * Math.log(v)/Math.LN10]);
 			    }
@@ -2932,7 +2932,7 @@ schematic = (function() {
 				    // for each requested time, interpolate waveform value
 				    for (var k = 1; k < tlist.length; k++) {
 					var t = tlist[k];
-					var v = interpolate(t,times,values); // xss-lint: disable=javascript-interpolate
+					var v = interpolate(t,times,values);
 					tvlist.push([t,v == undefined ? 'undefined' : v]);
 				    }
 				    // save results as list of [t,value] pairs
@@ -2978,7 +2978,7 @@ schematic = (function() {
 
 	// t is the time at which we want a value
 	// times is a list of timepoints from the simulation
-	function interpolate(t,times,values) { // xss-lint: disable=javascript-interpolate
+	function interpolate(t,times,values) {
 	    if (values == undefined) return undefined;
 
 	    for (var i = 0; i < times.length; i++)
@@ -5219,7 +5219,7 @@ schematic = (function() {
 	}
 	Wire.prototype = new Component();
 	Wire.prototype.constructor = Wire;
-    
+
         Wire.prototype.toString = function() {
 	    return edx.StringUtils.interpolate(
 		'<Wire ({x},{y}) ({x_plus_dx},{y_plus_dy})>',
@@ -5348,7 +5348,7 @@ schematic = (function() {
 		    y: this.y
 		});
 	}
-    
+
 	Ground.prototype.draw = function(c) {
 	    Component.prototype.draw.call(this,c);   // give superclass a shot
 	    this.draw_line(c,0,0,0,8);
diff --git a/common/lib/xmodule/xmodule/js/src/sequence/display.js b/common/lib/xmodule/xmodule/js/src/sequence/display.js
index 06f519bd3508ce5ee689876439e13f106417ccea..1a283e55b8450545f8b4ed2b831b0b0bd3baabf8 100644
--- a/common/lib/xmodule/xmodule/js/src/sequence/display.js
+++ b/common/lib/xmodule/xmodule/js/src/sequence/display.js
@@ -329,7 +329,6 @@
                 this.render(newPosition);
             } else {
                 alertTemplate = gettext('Sequence error! Cannot navigate to %(tab_name)s in the current SequenceModule. Please contact the course staff.');  // eslint-disable-line max-len
-                // xss-lint: disable=javascript-interpolate
                 alertText = interpolate(alertTemplate, {
                     tab_name: newPosition
                 }, true);
diff --git a/common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js b/common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
index df7ac15e0829460c035b459808ac04aeb27303ed..3179e8b1534ceab555c4f7d5fd796e32560fb679 100644
--- a/common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
+++ b/common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js
@@ -329,7 +329,6 @@ function() {
                     msg = ngettext('%(value)s second', '%(value)s seconds', value);
                     break;
                 }
-                // xss-lint: disable=javascript-interpolate
                 return interpolate(msg, {value: value}, true);
             };
 
diff --git a/common/static/js/capa/spec/formula_equation_preview_spec.js b/common/static/js/capa/spec/formula_equation_preview_spec.js
index ad0a903e0352b1b7be818f2e41739151e19eca6c..17c147a8d0cf7af68c029bb9010099b3163ce237 100644
--- a/common/static/js/capa/spec/formula_equation_preview_spec.js
+++ b/common/static/js/capa/spec/formula_equation_preview_spec.js
@@ -1,3 +1,32 @@
+describe('escapeSelector', function() {
+    'use strict';
+    var escapeSelector = window.escapeSelector;
+
+    it('correctly escapes css', function() {
+        // tests borrowed from
+        // https://github.com/jquery/jquery/blob/3edfa1bcdc50bca41ac58b2642b12f3feee03a3b/test/unit/selector.js#L2030
+        expect(escapeSelector('-')).toEqual('\\-');
+        expect(escapeSelector('-a')).toEqual('-a');
+        expect(escapeSelector('--')).toEqual('--');
+        expect(escapeSelector('--a')).toEqual('--a');
+        expect(escapeSelector('\uFFFD')).toEqual('\uFFFD');
+        expect(escapeSelector('\uFFFDb')).toEqual('\uFFFDb');
+        expect(escapeSelector('a\uFFFDb')).toEqual('a\uFFFDb');
+        expect(escapeSelector('1a')).toEqual('\\31 a');
+        expect(escapeSelector('a\0b')).toEqual('a\uFFFDb');
+        expect(escapeSelector('a3b')).toEqual('a3b');
+        expect(escapeSelector('-4a')).toEqual('-\\34 a');
+        expect(escapeSelector('\x01\x02\x1E\x1F')).toEqual('\\1 \\2 \\1e \\1f ');
+
+        // This is the important one; xblocks and course ids often contain invalid characters, so if these aren't
+        // escaped when embedding/searching xblock IDs using css selectors, bad things happen.
+        expect(escapeSelector('course-v1:edX+DemoX+Demo_Course')).toEqual('course-v1\\:edX\\+DemoX\\+Demo_Course');
+        expect(escapeSelector('block-v1:edX+DemoX+Demo_Course+type@sequential+block')).toEqual(
+          'block-v1\\:edX\\+DemoX\\+Demo_Course\\+type\\@sequential\\+block'
+        );
+    });
+});
+
 describe('Formula Equation Preview', function() {
     'use strict';
     var formulaEquationPreview = window.formulaEquationPreview;
diff --git a/common/static/js/capa/src/formula_equation_preview.js b/common/static/js/capa/src/formula_equation_preview.js
index 646ba0809c021da12426bc9339b329640b185746..8456171972620a9bb186d0acf7d2ef335b31420f 100644
--- a/common/static/js/capa/src/formula_equation_preview.js
+++ b/common/static/js/capa/src/formula_equation_preview.js
@@ -1,3 +1,38 @@
+function escapeSelector(id) {
+    'use strict';
+    // Wrapper around window.CSS.escape that uses a fallback method if CSS.escape is not available.  This is designed to
+    // serialize a string to be used as a valid css selector.  See
+    // https://drafts.csswg.org/cssom/#the-css.escape()-method For example, this can be used with xblock and course ids,
+    // which often contain invalid characters that must be escaped to function properly in css selectors.
+    // TODO: if this escaping is also required elsewhere, it may be useful to add a global CSS.escape polyfill and
+    // use that directly.
+
+    // CSS string/identifier serialization https://drafts.csswg.org/cssom/#common-serializing-idioms
+    // This code borrowed from https://api.jquery.com/jQuery.escapeSelector/ (source:
+    // https://github.com/jquery/jquery/blob/3edfa1bc/src/selector/escapeSelector.js). When we upgrade to jQuery 3.0, we
+    // can use $.escapeSelector() instead of this shim escapeSelector function.
+    var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; // eslint-disable-line no-control-regex
+    function fcssescape(ch, asCodePoint) {
+        if (asCodePoint) {
+            // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
+            if (ch === '\0') {
+                return '\uFFFD';
+            }
+            // Control characters and (dependent upon position) numbers get escaped as code points
+            return ch.slice(0, -1) + '\\' + ch.charCodeAt(ch.length - 1).toString(16) + ' ';
+        }
+        // Other potentially-special ASCII characters get backslash-escaped
+        return '\\' + ch;
+    }
+
+    if (window.CSS && window.CSS.escape) {
+        return window.CSS.escape(id);
+    } else {
+        // ensure string and then run the replacements
+        return (id + '').replace(rcssescape, fcssescape);
+    }
+}
+
 var formulaEquationPreview = {
     minDelay: 300,  // Minimum time between requests sent out.
     errorDelay: 1500  // Wait time before showing error (prevent frustration).
@@ -13,7 +48,7 @@ formulaEquationPreview.enable = function() {
     function setupInput() {
         var $this = $(this); // cache the jQuery object
 
-        var $preview = $('#' + this.id + '_preview');
+        var $preview = $('#' + escapeSelector(this.id) + '_preview');
         var inputData = {
             // These are the mutable values
 
diff --git a/lms/djangoapps/teams/static/teams/js/views/edit_team_members.js b/lms/djangoapps/teams/static/teams/js/views/edit_team_members.js
index 425d82e4773ca6adcc2c261443c2b3c9af992b3d..16aadbde49e4a80396899e0fccb0a7dc38fc58f2 100644
--- a/lms/djangoapps/teams/static/teams/js/views/edit_team_members.js
+++ b/lms/djangoapps/teams/static/teams/js/views/edit_team_members.js
@@ -52,7 +52,7 @@
 
                     _.each(this.model.get('membership'), function(membership) {
                         // eslint-disable-next-line no-undef
-                        dateJoined = interpolate( // xss-lint: disable=javascript-interpolate
+                        dateJoined = interpolate(
                             /* Translators: 'date' is a placeholder for a fuzzy,
                              * relative timestamp (see: https://github.com/rmm5t/jquery-timeago)
                              */
@@ -62,7 +62,7 @@
                         );
 
                         // eslint-disable-next-line no-undef
-                        lastActivity = interpolate( // xss-lint: disable=javascript-interpolate
+                        lastActivity = interpolate(
                             /* Translators: 'date' is a placeholder for a fuzzy,
                              * relative timestamp (see: https://github.com/rmm5t/jquery-timeago)
                              */
diff --git a/openedx/core/djangoapps/xblock/runtime/shims.py b/openedx/core/djangoapps/xblock/runtime/shims.py
index c9c172b4d9289c6ac56380cc11290e7333651aea..edf1d56951fd40750e6167c9a7a5e03b4e4f88ae 100644
--- a/openedx/core/djangoapps/xblock/runtime/shims.py
+++ b/openedx/core/djangoapps/xblock/runtime/shims.py
@@ -11,6 +11,7 @@ from django.core.cache import cache
 from django.template import TemplateDoesNotExist
 from django.utils.functional import cached_property
 from fs.memoryfs import MemoryFS
+from openedx.core.djangoapps.xblock.apps import get_xblock_app_config
 import six
 
 from edxmako.shortcuts import render_to_string
@@ -270,7 +271,12 @@ class RuntimeShim(object):
         Seems only to be used by capa. Remove this if capa can be refactored.
         """
         # TODO: Refactor capa to access this directly, don't bother the runtime. Then remove it from here.
-        return settings.STATIC_URL
+        static_url = settings.STATIC_URL
+        if static_url.startswith('/') and not static_url.startswith('//'):
+            # This is not a full URL - should start with https:// to support loading assets from an iframe sandbox
+            site_root_url = get_xblock_app_config().get_site_root_url()
+            static_url = site_root_url + static_url
+        return static_url
 
     @cached_property
     def user_is_staff(self):
diff --git a/scripts/xsslint/tests/test_linters.py b/scripts/xsslint/tests/test_linters.py
index ce18bfea13cb44b2c03dbeef91244207253e9a86..e3556fa80010579312d1f3bb1e912d460ec81773 100644
--- a/scripts/xsslint/tests/test_linters.py
+++ b/scripts/xsslint/tests/test_linters.py
@@ -403,27 +403,19 @@ class TestJavaScriptLinter(TestLinter):
         linter.check_javascript_file_is_safe(data['template'], results)
         self._validate_data_rules(data, results)
 
-    @data(
-        {'template': 'StringUtils.interpolate()', 'rule': None},
-        {'template': 'HtmlUtils.interpolateHtml()', 'rule': None},
-        {'template': 'interpolate(anything)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_interpolate},
-    )
-    def test_javascript_interpolate(self, data):
-        """
-        Test check_javascript_file_is_safe with interpolate()
-        """
-        linter = _build_javascript_linter()
-        results = FileResults('')
-
-        linter.check_javascript_file_is_safe(data['template'], results)
-
-        self._validate_data_rules(data, results)
-
     @data(
         {'template': '_.escape(message)', 'rule': None},
-        {'template': 'anything.escape(message)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_escape},
+        {'template': 'anything.escape(message)', 'rule': None},
+        {'template': 'anythingescape(message)', 'rule': None},
+        {'template': '$escape(message)', 'rule': None},
+        {'template': '_escape(message)', 'rule': None},
+        {'template': 'escape(message)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_escape},
+        {'template': '(escape(message))', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_escape},
+        {'template': '  escape(message))', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_escape},
+        {'template': 'window.escape(message)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_escape},
+        {'template': '(window.escape(message)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_escape},
     )
-    def test_javascript_interpolate(self, data):
+    def test_javascript_escape(self, data):
         """
         Test check_javascript_file_is_safe with interpolate()
         """
diff --git a/scripts/xsslint/xsslint/linters.py b/scripts/xsslint/xsslint/linters.py
index d345483f0d0fa5dd913db589495fc778eee06855..50ef4b0ba468f6178bc0d4d58b619d66fd9352f9 100644
--- a/scripts/xsslint/xsslint/linters.py
+++ b/scripts/xsslint/xsslint/linters.py
@@ -329,7 +329,6 @@ class JavaScriptLinter(BaseLinter):
         javascript_jquery_html='javascript-jquery-html',
         javascript_concat_html='javascript-concat-html',
         javascript_escape='javascript-escape',
-        javascript_interpolate='javascript-interpolate',
     )
 
     def __init__(self, underscore_linter, javascript_skip_dirs=None):
@@ -401,7 +400,6 @@ class JavaScriptLinter(BaseLinter):
             file_contents, "html", self.ruleset.javascript_jquery_html, no_caller_check,
             self._is_jquery_html_argument_safe, results
         )
-        self._check_javascript_interpolate(file_contents, results)
         self._check_javascript_escape(file_contents, results)
         self._check_concat_with_html(file_contents, self.ruleset.javascript_concat_html, results)
         self.underscore_linter.check_underscore_file_is_safe(file_contents, results)
@@ -435,37 +433,18 @@ class JavaScriptLinter(BaseLinter):
             expression = Expression(start_index)
         return expression
 
-    def _check_javascript_interpolate(self, file_contents, results):
-        """
-        Checks that interpolate() calls are safe.
-
-        Only use of StringUtils.interpolate() or HtmlUtils.interpolateText()
-        are safe.
-
-        Arguments:
-            file_contents: The contents of the JavaScript file.
-            results: A file results objects to which violations will be added.
-
-        """
-        # Ignores calls starting with "StringUtils.", because those are safe
-        regex = re.compile(r"(?<!StringUtils).interpolate\(")
-        for function_match in regex.finditer(file_contents):
-            expression = self._get_expression_for_function(file_contents, function_match)
-            results.violations.append(ExpressionRuleViolation(self.ruleset.javascript_interpolate, expression))
-
     def _check_javascript_escape(self, file_contents, results):
         """
-        Checks that only necessary escape() are used.
-
-        Allows for _.escape(), although this shouldn't be the recommendation.
+        Checks that escape() is not used. escape() is not recommended.
+        ref. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/escape
 
         Arguments:
             file_contents: The contents of the JavaScript file.
             results: A file results objects to which violations will be added.
 
         """
-        # Ignores calls starting with "_.", because those are safe
-        regex = regex = re.compile(r"(?<!_).escape\(")
+        # Regex to match uses of escape() or window.escape().
+        regex = re.compile(r"(?:^|(?<=window\.)|(?<![\w.$]))escape\(")
         for function_match in regex.finditer(file_contents):
             expression = self._get_expression_for_function(file_contents, function_match)
             results.violations.append(ExpressionRuleViolation(self.ruleset.javascript_escape, expression))
diff --git a/scripts/xsslint_thresholds.json b/scripts/xsslint_thresholds.json
index f1e89e8adb116ebc4af9048950416e30be9670c9..bd51cf7943a3f9202284019d551836cb8f3f9295 100644
--- a/scripts/xsslint_thresholds.json
+++ b/scripts/xsslint_thresholds.json
@@ -2,7 +2,6 @@
     "rules": {
         "javascript-concat-html": 100,
         "javascript-escape": 5,
-        "javascript-interpolate": 9,
         "javascript-jquery-append": 50,
         "javascript-jquery-html": 112,
         "javascript-jquery-insert-into-target": 18,