diff --git a/.changelog/9847.txt b/.changelog/9847.txt
new file mode 100644
index 0000000000..a3010258b3
--- /dev/null
+++ b/.changelog/9847.txt
@@ -0,0 +1,3 @@
+```release-note:improvement
+ui: support stricter content security policies
+```
diff --git a/ui/packages/consul-ui/app/initializers/ivy-codemirror.js b/ui/packages/consul-ui/app/initializers/ivy-codemirror.js
deleted file mode 100644
index c4f3436057..0000000000
--- a/ui/packages/consul-ui/app/initializers/ivy-codemirror.js
+++ /dev/null
@@ -1,11 +0,0 @@
-export function initialize(application) {
- const IvyCodeMirrorComponent = application.resolveRegistration('component:ivy-codemirror');
- // Make sure ivy-codemirror respects/maintains a `name=""` attribute
- IvyCodeMirrorComponent.reopen({
- attributeBindings: ['name'],
- });
-}
-
-export default {
- initialize,
-};
diff --git a/ui/packages/consul-ui/app/instance-initializers/ivy-codemirror.js b/ui/packages/consul-ui/app/instance-initializers/ivy-codemirror.js
new file mode 100644
index 0000000000..f347060efe
--- /dev/null
+++ b/ui/packages/consul-ui/app/instance-initializers/ivy-codemirror.js
@@ -0,0 +1,30 @@
+/* globals CodeMirror */
+export function initialize(application) {
+ const appName = application.application.name;
+ const doc = application.lookup('service:-document');
+ // pick codemirror syntax highlighting paths out of index.html
+ const fs = JSON.parse(doc.querySelector(`[data-${appName}-fs]`).textContent);
+ // configure syntax highlighting for CodeMirror
+ CodeMirror.modeURL = {
+ replace: function(n, mode) {
+ switch (mode) {
+ case 'javascript':
+ return fs['codemirror/mode/javascript/javascript.js'];
+ case 'ruby':
+ return fs['codemirror/mode/ruby/ruby.js'];
+ case 'yaml':
+ return fs['codemirror/mode/yaml/yaml.js'];
+ }
+ },
+ };
+
+ const IvyCodeMirrorComponent = application.resolveRegistration('component:ivy-codemirror');
+ // Make sure ivy-codemirror respects/maintains a `name=""` attribute
+ IvyCodeMirrorComponent.reopen({
+ attributeBindings: ['name'],
+ });
+}
+
+export default {
+ initialize,
+};
diff --git a/ui/packages/consul-ui/ember-cli-build.js b/ui/packages/consul-ui/ember-cli-build.js
index c91ee00a2b..8312bdf778 100644
--- a/ui/packages/consul-ui/ember-cli-build.js
+++ b/ui/packages/consul-ui/ember-cli-build.js
@@ -37,6 +37,10 @@ module.exports = function(defaults) {
plugins: ['@babel/plugin-proposal-object-rest-spread'],
sourceMaps: sourcemaps ? 'inline' : false,
},
+ autoImport: {
+ // allows use of a CSP without 'unsafe-eval' directive
+ forbidEval: true,
+ },
codemirror: {
keyMaps: ['sublime'],
addonFiles: [
diff --git a/ui/packages/consul-ui/lib/startup/templates/body.html.js b/ui/packages/consul-ui/lib/startup/templates/body.html.js
index cff576754d..10fa4cecd6 100644
--- a/ui/packages/consul-ui/lib/startup/templates/body.html.js
+++ b/ui/packages/consul-ui/lib/startup/templates/body.html.js
@@ -16,24 +16,20 @@ module.exports = ({ appName, environment, rootURL, config }) => `
}
+
${environment === 'test' ? `` : ``}
-
${
@@ -42,19 +38,5 @@ ${environment === 'production' ? `{{jsonEncode .}}` : JSON.stringify(config.oper
: ``
}
-
${environment === 'test' ? `` : ``}
`;
diff --git a/ui/packages/consul-ui/server/index.js b/ui/packages/consul-ui/server/index.js
index 56f8bcebce..a2abdb55e8 100644
--- a/ui/packages/consul-ui/server/index.js
+++ b/ui/packages/consul-ui/server/index.js
@@ -25,6 +25,14 @@ module.exports = function(app, options) {
}
next();
});
+
+ // sets the base CSP policy for the UI
+ app.use(function(request, response, next) {
+ response.set({
+ 'Content-Security-Policy': `default-src 'self' ws: localhost:${options.liveReloadPort} http: localhost:${options.liveReloadPort}; img-src 'self' data: ; style-src 'self' 'unsafe-inline'`,
+ });
+ next();
+ });
// Serve the coverage folder for easy viewing during development
app.use('/coverage', express.static('coverage'));
};
diff --git a/ui/packages/consul-ui/vendor/init.js b/ui/packages/consul-ui/vendor/init.js
index 63d99761ff..7852b86490 100644
--- a/ui/packages/consul-ui/vendor/init.js
+++ b/ui/packages/consul-ui/vendor/init.js
@@ -1,4 +1,20 @@
(function(doc, appName) {
+ const fs = JSON.parse(doc.querySelector(`[data-${appName}-fs]`).textContent);
+ const appendScript = function(src) {
+ var $script = doc.createElement('script');
+ $script.src = src;
+ doc.body.appendChild($script);
+ };
+
+ // polyfills
+ if (!('TextDecoder' in window)) {
+ appendScript(fs['text-encoding/encoding-indexes.js']);
+ appendScript(fs['text-encoding/encoding.js']);
+ }
+ if (!(window.CSS && window.CSS.escape)) {
+ appendScript(fs['css.escape/css.escape.js']);
+ }
+
try {
const $appMeta = doc.querySelector(`[name="${appName}/config/environment"]`);
// pick out the operatorConfig from our application/json script tag