├── .gitignore ├── README.md ├── lib ├── index.js └── schema.js ├── package-lock.json ├── package.json └── test ├── callbacks.js ├── chrome.js ├── firefox.js ├── generic.js ├── ie.js ├── index.js ├── safari.js └── templates └── nonce.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | coverage.html 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## blankie 2 | 3 | A CSP plugin for [hapi](https://github.com/hapijs/hapi). 4 | 5 | ### Usage 6 | 7 | This plugin depends on [scooter](https://github.com/hapijs/scooter) to function. 8 | 9 | To use it: 10 | 11 | ```javascript 12 | 'use strict'; 13 | 14 | const Hapi = require('@hapi/hapi'); 15 | const Blankie = require('blankie'); 16 | const Scooter = require('@hapi/scooter'); 17 | 18 | const internals = {}; 19 | 20 | const server = Hapi.server(); 21 | 22 | internals.init = async () => { 23 | 24 | await server.register([Scooter, { 25 | plugin: Blankie, 26 | options: {} // specify options here 27 | }]); 28 | 29 | await server.start(); 30 | }; 31 | 32 | internals.init().catch((err) => { 33 | 34 | throw err; 35 | }); 36 | ``` 37 | 38 | Options may also be set on a per-route basis: 39 | 40 | ```javascript 41 | 'use strict'; 42 | 43 | const Hapi = require('@hapi/hapi'); 44 | const Blankie = require('blankie'); 45 | const Scooter = require('@hapi/scooter'); 46 | 47 | const server = Hapi.server(); 48 | 49 | server.route({ 50 | method: 'GET', 51 | path: '/something', 52 | config: { 53 | handler: (request, h) => { 54 | 55 | return 'these settings are changed'; 56 | }, 57 | plugins: { 58 | blankie: { 59 | scriptSrc: 'self' 60 | } 61 | } 62 | } 63 | }); 64 | ``` 65 | 66 | Note that this setting will *NOT* be merged with your server-wide settings. 67 | 68 | You may also set `config.plugins.blankie` equal to `false` on a route to disable CSP headers completely for that route. 69 | 70 | ### Options 71 | 72 | * `baseUri`: Values for `base-uri` directive. Defaults `'self'`. 73 | * `childSrc`: Values for `child-src` directive. 74 | * `connectSrc`: Values for the `connect-src` directive. Defaults `'self'`. 75 | * `defaultSrc`: Values for the `default-src` directive. Defaults to `'none'`. 76 | * `fontSrc`: Values for the `font-src` directive. 77 | * `formAction`: Values for the `form-action` directive. 78 | * `frameAncestors`: Values for the `frame-ancestors` directive. 79 | * `frameSrc`: Values for the `frame-src` directive. 80 | * `imgSrc`: Values for the `image-src` directive. Defaults to `'self'`. 81 | * `manifestSrc`: Values for the `manifest-src` directive. 82 | * `mediaSrc`: Values for the `media-src` directive. 83 | * `objectSrc`: Values for the `object-src` directive. 84 | * `oldSafari`: Force enabling buggy CSP for Safari 5. 85 | * `pluginTypes`: Values for the `plugin-types` directive. 86 | * `reflectedXss`: Value for the `reflected-xss` directive. Must be one of `'allow'`, `'block'` or `'filter'`. 87 | * `reportOnly`: Append '-Report-Only' to the name of the CSP header to enable report only mode. 88 | * `reportUri`: Value for the `report-uri` directive. This should be the path to a route that accepts CSP violation reports. 89 | * `requireSriFor`: Value for `require-sri-for` directive. 90 | * `sandbox`: Values for the `sandbox` directive. May be a boolean or one of `'allow-forms'`, `'allow-same-origin'`, `'allow-scripts'` or `'allow-top-navigation'`. 91 | * `scriptSrc`: Values for the `script-src` directive. Defaults to `'self'`. 92 | * `styleSrc`: Values for the `style-src` directive. Defaults to `'self'`. 93 | * `workerSrc`: Values for the `worker-src` directive. Defaults to `'self'`. 94 | * `generateNonces`: Whether or not to automatically generate nonces. Defaults to `true`. May be a boolean or one of `'script'` or `'style'`. When enabled your templates rendered through [vision](https://github.com/hapijs/vision) will have `script-nonce` and/or `style-nonce` automatically added to their context, additionally `request.plugins.blankie.nonces` will contain one or both of the `'script'` and `'style'` properties containing these values for use outside of vision. 95 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Crypto = require('crypto'); 4 | const Schema = require('./schema'); 5 | const Hoek = require('@hapi/hoek'); 6 | 7 | const internals = {}; 8 | 9 | 10 | internals.arrayValues = [ 11 | 'baseUri', 12 | 'childSrc', 13 | 'connectSrc', 14 | 'defaultSrc', 15 | 'fontSrc', 16 | 'formAction', 17 | 'frameAncestors', 18 | 'frameSrc', 19 | 'imgSrc', 20 | 'manifestSrc', 21 | 'mediaSrc', 22 | 'objectSrc', 23 | 'pluginTypes', 24 | 'requireSriFor', 25 | 'sandbox', 26 | 'scriptSrc', 27 | 'styleSrc', 28 | 'workerSrc', 29 | 'xhrSrc' 30 | ]; 31 | 32 | internals.stringValues = [ 33 | 'reportUri', 34 | 'reflectedXss' 35 | ]; 36 | 37 | internals.directiveNames = internals.arrayValues.concat(internals.stringValues); 38 | 39 | internals.directiveMap = { 40 | 'baseUri': 'base-uri', 41 | 'childSrc': 'child-src', 42 | 'connectSrc': 'connect-src', 43 | 'defaultSrc': 'default-src', 44 | 'fontSrc': 'font-src', 45 | 'formAction': 'form-action', 46 | 'frameAncestors': 'frame-ancestors', 47 | 'frameSrc': 'frame-src', 48 | 'imgSrc': 'img-src', 49 | 'manifestSrc': 'manifest-src', 50 | 'mediaSrc': 'media-src', 51 | 'objectSrc': 'object-src', 52 | 'pluginTypes': 'plugin-types', 53 | 'reflectedXss': 'reflected-xss', 54 | 'reportUri': 'report-uri', 55 | 'requireSriFor': 'require-sri-for', 56 | 'scriptSrc': 'script-src', 57 | 'styleSrc': 'style-src', 58 | 'workerSrc': 'worker-src', 59 | 'xhrSrc': 'xhr-src' 60 | }; 61 | 62 | internals.allHeaders = [ 63 | 'Content-Security-Policy', 64 | 'X-Content-Security-Policy', 65 | 'X-WebKit-CSP' 66 | ]; 67 | 68 | internals.needQuotes = [ 69 | 'self', 70 | 'none', 71 | 'unsafe-inline', 72 | 'unsafe-eval', 73 | 'inline-script', 74 | 'eval-script', 75 | 'strict-dynamic' 76 | ]; 77 | 78 | internals.keyToConfigMap = { 79 | scriptSrc: 'script', 80 | styleSrc: 'style' 81 | }; 82 | 83 | internals.nonceShouldBeGenerated = function (options, key) { 84 | 85 | return options.generateNonces === true || options.generateNonces === internals.keyToConfigMap[key]; 86 | }; 87 | 88 | 89 | internals.generateNonce = function () { 90 | 91 | let bytes; 92 | try { 93 | bytes = Crypto.randomBytes(16); 94 | } 95 | catch (e) { 96 | bytes = Crypto.pseudoRandomBytes(16); 97 | } 98 | 99 | return bytes.toString('hex'); 100 | }; 101 | 102 | 103 | internals.generateIEPolicy = function (options, request) { 104 | 105 | // IE only supports the sandbox directive 106 | const sandboxPolicy = Hoek.clone(options).sandbox; 107 | return internals.generatePolicy({ sandbox: sandboxPolicy }, request); 108 | }; 109 | 110 | 111 | internals.generateFirefoxPolicy = function (options, request) { 112 | 113 | // connect-src -> xhr-src 114 | options.xhrSrc = options.connectSrc; 115 | delete options.connectSrc; 116 | 117 | // no sandbox support 118 | delete options.sandbox; 119 | 120 | // "unsafe-inline" -> "inline-script" 121 | // "unsafe-eval" -> "eval-script" 122 | options.scriptSrc = options.scriptSrc.map((value) => { 123 | 124 | if (value === '\'unsafe-inline\'') { 125 | return '\'inline-script\''; 126 | } 127 | else if (value === '\'unsafe-eval\'') { 128 | return '\'eval-script\''; 129 | } 130 | 131 | return value; 132 | }); 133 | 134 | // remove "unsafe-inline" and "unsafe-eval" from other directives 135 | internals.arrayValues.forEach((key) => { 136 | 137 | if (!options[key]) { 138 | return false; 139 | } 140 | 141 | options[key] = options[key].filter((value) => { 142 | 143 | return value !== '\'unsafe-inline\'' && value !== '\'unsafe-eval\''; 144 | }); 145 | 146 | if (!options[key].length) { 147 | options[key] = undefined; 148 | } 149 | }); 150 | 151 | return internals.generatePolicy(options, request); 152 | }; 153 | 154 | 155 | internals.generateFirefox4Policy = function (options, request) { 156 | 157 | let policy = internals.generateFirefoxPolicy(options, request); 158 | 159 | // firefox 4 uses "allow" instead of "default-src" 160 | policy = policy.replace('default-src', 'allow'); 161 | 162 | return policy; 163 | }; 164 | 165 | 166 | internals.generatePolicy = function (options, request) { 167 | 168 | const policy = []; 169 | 170 | // map the camel case names to proper directive names 171 | // and join their values into strings 172 | internals.directiveNames.forEach((key) => { 173 | 174 | if (!options[key]) { 175 | return; 176 | } 177 | 178 | const directive = internals.directiveMap[key] || key; 179 | if (internals.stringValues.indexOf(key) >= 0) { 180 | policy.push(`${directive} ${options[key]}`); 181 | return; 182 | } 183 | 184 | if (key === 'sandbox' && options[key] === true) { 185 | // it's allowed to have a sandbox directive with no value 186 | policy.push('sandbox'); 187 | return; 188 | } 189 | 190 | if ((key === 'scriptSrc' || key === 'styleSrc') && internals.nonceShouldBeGenerated(options, key)) { 191 | 192 | const nonce = request.plugins.blankie ? request.plugins.blankie.nonces[internals.keyToConfigMap[key]] : internals.generateNonce(); 193 | const sources = Hoek.clone(options[key]); 194 | sources.push(`'nonce-${nonce}'`); 195 | policy.push(`${directive} ${sources.join(' ')}`); 196 | if (request.response.variety === 'view') { 197 | request.response.source.context = Hoek.applyToDefaults({ nonce }, request.response.source.context || {}); 198 | request.response.source.context[`${key.slice(0, -3)}-nonce`] = nonce; 199 | } 200 | 201 | return; 202 | } 203 | 204 | policy.push(`${directive} ${options[key].join(' ')}`); 205 | }); 206 | 207 | return policy.join(';'); 208 | }; 209 | 210 | 211 | internals.attachNonces = function (request, h) { 212 | 213 | if (request.method === 'options' || 214 | request.route.settings.plugins.blankie === false) { 215 | 216 | return h.continue; 217 | } 218 | 219 | let options; 220 | 221 | if (request.route.settings.plugins.blankie) { 222 | options = internals.validateOptions(request.route.settings.plugins.blankie); 223 | 224 | if (options instanceof Error) { 225 | request.server.log(['error', 'blankie'], `Invalid blankie configuration on route: ${request.route.path}`); 226 | return h.continue; 227 | } 228 | } 229 | else if (internals.cspCallback) { 230 | options = internals.cspCallback(request); 231 | 232 | options = internals.validateOptions(options); 233 | if (options instanceof Error) { 234 | request.server.log(['error', 'blankie'], 'Invalid blankie configuration from CSP Callback'); 235 | return h.continue; 236 | } 237 | } 238 | else { 239 | options = Hoek.clone(internals.options); 240 | } 241 | 242 | ['scriptSrc', 'styleSrc'].forEach((key) => { 243 | 244 | if (internals.nonceShouldBeGenerated(options, key)) { 245 | 246 | const nonce = internals.generateNonce(); 247 | request.plugins.blankie = Object.assign({}, request.plugins.blankie); 248 | request.plugins.blankie.nonces = Object.assign({}, request.plugins.blankie.nonces); 249 | request.plugins.blankie.nonces[internals.keyToConfigMap[key]] = nonce; 250 | } 251 | }); 252 | 253 | return h.continue; 254 | }; 255 | 256 | 257 | internals.addHeaders = function (request, h) { 258 | 259 | const contentType = (request.response.headers || request.response.output.headers)['content-type']; 260 | 261 | if (request.method === 'options' || 262 | request.route.settings.plugins.blankie === false || 263 | (contentType && contentType !== 'text/html')) { 264 | 265 | return h.continue; 266 | } 267 | 268 | let options; 269 | 270 | if (request.route.settings.plugins.blankie) { 271 | options = internals.validateOptions(request.route.settings.plugins.blankie); 272 | 273 | if (options instanceof Error) { 274 | request.server.log(['error', 'blankie'], `Invalid blankie configuration on route: ${request.route.path}`); 275 | return h.continue; 276 | } 277 | } 278 | else if (internals.cspCallback) { 279 | options = internals.cspCallback(request); 280 | 281 | options = internals.validateOptions(options); 282 | if (options instanceof Error) { 283 | request.server.log(['error', 'blankie'], 'Invalid blankie configuration from CSP Callback'); 284 | return h.continue; 285 | } 286 | } 287 | else { 288 | options = Hoek.clone(internals.options); 289 | } 290 | 291 | const userAgent = request.plugins.scooter || {}; 292 | const version = parseInt(userAgent.major, 10); 293 | let headerName = 'Content-Security-Policy'; 294 | let policy; 295 | 296 | switch (userAgent.family) { 297 | case 'Chrome': 298 | if (version >= 14 && version <= 25) { 299 | headerName = 'X-WebKit-CSP'; 300 | } 301 | 302 | policy = internals.generatePolicy(options, request); 303 | 304 | break; 305 | case 'Firefox': 306 | if (version === 4) { 307 | headerName = 'X-Content-Security-Policy'; 308 | policy = internals.generateFirefox4Policy(options, request); 309 | } 310 | else if (version >= 5 && version <= 23) { 311 | headerName = 'X-Content-Security-Policy'; 312 | policy = internals.generateFirefoxPolicy(options, request); 313 | } 314 | else { 315 | policy = internals.generatePolicy(options, request); 316 | } 317 | 318 | break; 319 | case 'IE': 320 | headerName = 'X-Content-Security-Policy'; 321 | policy = internals.generateIEPolicy(options, request); 322 | 323 | break; 324 | case 'Safari': 325 | if (version === 6 || 326 | (version === 5 && options.oldSafari)) { 327 | headerName = 'X-WebKit-CSP'; 328 | } 329 | 330 | policy = internals.generatePolicy(options, request); 331 | 332 | break; 333 | default: 334 | policy = internals.generatePolicy(options, request); 335 | 336 | break; 337 | } 338 | 339 | if (options.reportOnly) { 340 | headerName += '-Report-Only'; 341 | } 342 | 343 | if (!request.response.isBoom) { 344 | request.response.headers[headerName] = policy; 345 | } 346 | else { 347 | request.response.output.headers[headerName] = policy; 348 | } 349 | 350 | return h.continue; 351 | }; 352 | 353 | 354 | internals.validateOptions = function (options) { 355 | 356 | const { error, value } = Schema.validate(options); 357 | 358 | if (error) { 359 | return error; 360 | } 361 | 362 | internals.arrayValues.forEach((key) => { 363 | 364 | if (value[key] !== undefined) { 365 | if (key === 'sandbox' && value[key] === true) { 366 | return; 367 | } 368 | 369 | value[key] = value[key].map((val) => { 370 | 371 | if (internals.needQuotes.indexOf(val) !== -1) { 372 | return `'${val}'`; 373 | } 374 | 375 | return val; 376 | }); 377 | } 378 | }); 379 | 380 | return value; 381 | }; 382 | 383 | exports.plugin = { 384 | register: function (server, options) { 385 | 386 | internals.cspCallback = null; 387 | 388 | if (typeof options === 'function') { 389 | internals.cspCallback = options; 390 | } 391 | else { 392 | internals.options = internals.validateOptions(options); 393 | if (internals.options instanceof Error) { 394 | throw internals.options; 395 | } 396 | } 397 | 398 | server.ext('onPreHandler', internals.attachNonces); 399 | server.ext('onPreResponse', internals.addHeaders); 400 | }, 401 | dependencies: ['@hapi/scooter'], 402 | once: true, 403 | pkg: require('../package.json') 404 | }; 405 | -------------------------------------------------------------------------------- /lib/schema.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Joi = require('@hapi/joi'); 4 | 5 | module.exports = Joi.object({ 6 | baseUri: Joi.array().items(Joi.string()).single().default(['self']), 7 | childSrc: Joi.array().items(Joi.string()).single(), 8 | connectSrc: Joi.array().items(Joi.string()).single().default(['self']), 9 | defaultSrc: Joi.array().items(Joi.string()).single().default(['none']), 10 | fontSrc: Joi.array().items(Joi.string()).single(), 11 | formAction: Joi.array().items(Joi.string()).single(), 12 | frameAncestors: Joi.array().items(Joi.string()).single(), 13 | frameSrc: Joi.array().items(Joi.string()).single(), 14 | imgSrc: Joi.array().items(Joi.string()).single().default(['self']), 15 | manifestSrc: Joi.array().items(Joi.string()).single(), 16 | mediaSrc: Joi.array().items(Joi.string()).single(), 17 | objectSrc: Joi.array().items(Joi.string()).single(), 18 | oldSafari: Joi.boolean(), 19 | pluginTypes: Joi.array().items(Joi.string()).single(), 20 | reflectedXss: Joi.string().valid('allow', 'block', 'filter'), 21 | reportOnly: Joi.boolean(), 22 | reportUri: Joi.string(), 23 | requireSriFor: Joi.array().items(Joi.string()).single(), 24 | sandbox: [ 25 | Joi.array().items(Joi.string().valid('allow-forms', 'allow-same-origin', 'allow-scripts', 'allow-top-navigation')).single(), 26 | Joi.boolean() 27 | ], 28 | scriptSrc: Joi.array().items(Joi.string()).single().default(['self']) 29 | .when('generateNonces', { is: [false, 'style'], then: Joi.array().items(Joi.string().valid('strict-dynamic').forbidden()) }), 30 | styleSrc: Joi.array().items(Joi.string()).single().default(['self']) 31 | .when('generateNonces', { is: [false, 'script'], then: Joi.array().items(Joi.string().valid('strict-dynamic').forbidden()) }), 32 | workerSrc: Joi.array().items(Joi.string()).single().default(['self']), 33 | generateNonces: Joi.alternatives().try(Joi.boolean(), Joi.string().valid('script', 'style')).default(true) 34 | }).with('reportOnly', 'reportUri'); 35 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blankie", 3 | "version": "5.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/code-frame": { 8 | "version": "7.8.3", 9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", 10 | "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", 11 | "dev": true, 12 | "requires": { 13 | "@babel/highlight": "^7.8.3" 14 | } 15 | }, 16 | "@babel/generator": { 17 | "version": "7.8.4", 18 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", 19 | "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", 20 | "dev": true, 21 | "requires": { 22 | "@babel/types": "^7.8.3", 23 | "jsesc": "^2.5.1", 24 | "lodash": "^4.17.13", 25 | "source-map": "^0.5.0" 26 | }, 27 | "dependencies": { 28 | "source-map": { 29 | "version": "0.5.7", 30 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 31 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", 32 | "dev": true 33 | } 34 | } 35 | }, 36 | "@babel/helper-function-name": { 37 | "version": "7.8.3", 38 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", 39 | "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", 40 | "dev": true, 41 | "requires": { 42 | "@babel/helper-get-function-arity": "^7.8.3", 43 | "@babel/template": "^7.8.3", 44 | "@babel/types": "^7.8.3" 45 | } 46 | }, 47 | "@babel/helper-get-function-arity": { 48 | "version": "7.8.3", 49 | "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", 50 | "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", 51 | "dev": true, 52 | "requires": { 53 | "@babel/types": "^7.8.3" 54 | } 55 | }, 56 | "@babel/helper-split-export-declaration": { 57 | "version": "7.8.3", 58 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", 59 | "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", 60 | "dev": true, 61 | "requires": { 62 | "@babel/types": "^7.8.3" 63 | } 64 | }, 65 | "@babel/highlight": { 66 | "version": "7.8.3", 67 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", 68 | "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", 69 | "dev": true, 70 | "requires": { 71 | "chalk": "^2.0.0", 72 | "esutils": "^2.0.2", 73 | "js-tokens": "^4.0.0" 74 | } 75 | }, 76 | "@babel/parser": { 77 | "version": "7.8.4", 78 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", 79 | "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", 80 | "dev": true 81 | }, 82 | "@babel/template": { 83 | "version": "7.8.3", 84 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", 85 | "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", 86 | "dev": true, 87 | "requires": { 88 | "@babel/code-frame": "^7.8.3", 89 | "@babel/parser": "^7.8.3", 90 | "@babel/types": "^7.8.3" 91 | } 92 | }, 93 | "@babel/traverse": { 94 | "version": "7.8.4", 95 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", 96 | "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", 97 | "dev": true, 98 | "requires": { 99 | "@babel/code-frame": "^7.8.3", 100 | "@babel/generator": "^7.8.4", 101 | "@babel/helper-function-name": "^7.8.3", 102 | "@babel/helper-split-export-declaration": "^7.8.3", 103 | "@babel/parser": "^7.8.4", 104 | "@babel/types": "^7.8.3", 105 | "debug": "^4.1.0", 106 | "globals": "^11.1.0", 107 | "lodash": "^4.17.13" 108 | } 109 | }, 110 | "@babel/types": { 111 | "version": "7.8.3", 112 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", 113 | "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", 114 | "dev": true, 115 | "requires": { 116 | "esutils": "^2.0.2", 117 | "lodash": "^4.17.13", 118 | "to-fast-properties": "^2.0.0" 119 | } 120 | }, 121 | "@hapi/accept": { 122 | "version": "5.0.1", 123 | "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-5.0.1.tgz", 124 | "integrity": "sha512-fMr4d7zLzsAXo28PRRQPXR1o2Wmu+6z+VY1UzDp0iFo13Twj8WePakwXBiqn3E1aAlTpSNzCXdnnQXFhst8h8Q==", 125 | "dev": true, 126 | "requires": { 127 | "@hapi/boom": "9.x.x", 128 | "@hapi/hoek": "9.x.x" 129 | } 130 | }, 131 | "@hapi/address": { 132 | "version": "4.0.0", 133 | "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.0.0.tgz", 134 | "integrity": "sha512-GDDpkCdSUfkQCznmWUHh9dDN85BWf/V8TFKQ2JLuHdGB4Yy3YTEGBzZxoBNxfNBEvreSR/o+ZxBBSNNEVzY+lQ==", 135 | "requires": { 136 | "@hapi/hoek": "^9.0.0" 137 | } 138 | }, 139 | "@hapi/ammo": { 140 | "version": "5.0.1", 141 | "resolved": "https://registry.npmjs.org/@hapi/ammo/-/ammo-5.0.1.tgz", 142 | "integrity": "sha512-FbCNwcTbnQP4VYYhLNGZmA76xb2aHg9AMPiy18NZyWMG310P5KdFGyA9v2rm5ujrIny77dEEIkMOwl0Xv+fSSA==", 143 | "dev": true, 144 | "requires": { 145 | "@hapi/hoek": "9.x.x" 146 | } 147 | }, 148 | "@hapi/b64": { 149 | "version": "5.0.0", 150 | "resolved": "https://registry.npmjs.org/@hapi/b64/-/b64-5.0.0.tgz", 151 | "integrity": "sha512-ngu0tSEmrezoiIaNGG6rRvKOUkUuDdf4XTPnONHGYfSGRmDqPZX5oJL6HAdKTo1UQHECbdB4OzhWrfgVppjHUw==", 152 | "dev": true, 153 | "requires": { 154 | "@hapi/hoek": "9.x.x" 155 | } 156 | }, 157 | "@hapi/boom": { 158 | "version": "9.0.0", 159 | "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.0.0.tgz", 160 | "integrity": "sha512-D+Or4yahLq3L7D1Jf0fR1+Lgr+HPK1lej8tc6hS/fBLmK66XdpvTyKv8YUR5ls1GeQy+KGtbpKAs+ZxyzNtUyA==", 161 | "dev": true, 162 | "requires": { 163 | "@hapi/hoek": "9.x.x" 164 | } 165 | }, 166 | "@hapi/bossy": { 167 | "version": "5.0.0", 168 | "resolved": "https://registry.npmjs.org/@hapi/bossy/-/bossy-5.0.0.tgz", 169 | "integrity": "sha512-/P89CNhRos1/rcytf19erNvnGsGDWuC7UxBRfoKIqkKHthnG9qRIRZCc5gmc7i/yp5ettKWqiYSf65HKDPz2bg==", 170 | "dev": true, 171 | "requires": { 172 | "@hapi/boom": "9.x.x", 173 | "@hapi/hoek": "9.x.x", 174 | "@hapi/joi": "17.x.x" 175 | } 176 | }, 177 | "@hapi/bounce": { 178 | "version": "2.0.0", 179 | "resolved": "https://registry.npmjs.org/@hapi/bounce/-/bounce-2.0.0.tgz", 180 | "integrity": "sha512-JesW92uyzOOyuzJKjoLHM1ThiOvHPOLDHw01YV8yh5nCso7sDwJho1h0Ad2N+E62bZyz46TG3xhAi/78Gsct6A==", 181 | "dev": true, 182 | "requires": { 183 | "@hapi/boom": "9.x.x", 184 | "@hapi/hoek": "9.x.x" 185 | } 186 | }, 187 | "@hapi/bourne": { 188 | "version": "2.0.0", 189 | "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz", 190 | "integrity": "sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg==", 191 | "dev": true 192 | }, 193 | "@hapi/call": { 194 | "version": "8.0.0", 195 | "resolved": "https://registry.npmjs.org/@hapi/call/-/call-8.0.0.tgz", 196 | "integrity": "sha512-4xHIWWqaIDQlVU88XAnomACSoC7iWUfaLfdu2T7I0y+HFFwZUrKKGfwn6ik4kwKsJRMnOliG3UXsF8V/94+Lkg==", 197 | "dev": true, 198 | "requires": { 199 | "@hapi/address": "4.x.x", 200 | "@hapi/boom": "9.x.x", 201 | "@hapi/hoek": "9.x.x" 202 | } 203 | }, 204 | "@hapi/catbox": { 205 | "version": "11.0.1", 206 | "resolved": "https://registry.npmjs.org/@hapi/catbox/-/catbox-11.0.1.tgz", 207 | "integrity": "sha512-CsdannMSzWqLcJ7rXT55JGAzoR+BPXesKn9POOrF0A0wsumbUwHP7vxBUH/21YitcM/dLxjUfphkRAQT+XaoyQ==", 208 | "dev": true, 209 | "requires": { 210 | "@hapi/boom": "9.x.x", 211 | "@hapi/hoek": "9.x.x", 212 | "@hapi/joi": "17.x.x", 213 | "@hapi/podium": "4.x.x" 214 | } 215 | }, 216 | "@hapi/catbox-memory": { 217 | "version": "5.0.0", 218 | "resolved": "https://registry.npmjs.org/@hapi/catbox-memory/-/catbox-memory-5.0.0.tgz", 219 | "integrity": "sha512-ByuxVJPHNaXwLzbBv4GdTr6ccpe1nG+AfYt+8ftDWEJY7EWBWzD+Klhy5oPTDGzU26pNUh1e7fcYI1ILZRxAXQ==", 220 | "dev": true, 221 | "requires": { 222 | "@hapi/boom": "9.x.x", 223 | "@hapi/hoek": "9.x.x" 224 | } 225 | }, 226 | "@hapi/code": { 227 | "version": "8.0.1", 228 | "resolved": "https://registry.npmjs.org/@hapi/code/-/code-8.0.1.tgz", 229 | "integrity": "sha512-umAOdgEjv1o2PbOQRpiy+XWb2RnHGMejhUUVR41vIkzVtBXQnql0wTU2UNq2/vI6KJx1ReR/P999QGFiSLk5hQ==", 230 | "dev": true, 231 | "requires": { 232 | "@hapi/hoek": "9.x.x" 233 | } 234 | }, 235 | "@hapi/content": { 236 | "version": "5.0.2", 237 | "resolved": "https://registry.npmjs.org/@hapi/content/-/content-5.0.2.tgz", 238 | "integrity": "sha512-mre4dl1ygd4ZyOH3tiYBrOUBzV7Pu/EOs8VLGf58vtOEECWed8Uuw6B4iR9AN/8uQt42tB04qpVaMyoMQh0oMw==", 239 | "dev": true, 240 | "requires": { 241 | "@hapi/boom": "9.x.x" 242 | } 243 | }, 244 | "@hapi/cryptiles": { 245 | "version": "5.0.0", 246 | "resolved": "https://registry.npmjs.org/@hapi/cryptiles/-/cryptiles-5.0.0.tgz", 247 | "integrity": "sha512-Yq43ti9N51Z7jbm0Q7YVCcofA+4Gh5wsBX/jZ++Z+FM8GYfBQ1WmI9ufZSL+BVX8vRxzDkdQ2fKoG6cxOQlnVQ==", 248 | "dev": true, 249 | "requires": { 250 | "@hapi/boom": "9.x.x" 251 | } 252 | }, 253 | "@hapi/eslint-config-hapi": { 254 | "version": "13.0.2", 255 | "resolved": "https://registry.npmjs.org/@hapi/eslint-config-hapi/-/eslint-config-hapi-13.0.2.tgz", 256 | "integrity": "sha512-LtTYBSWdBmv9JfMWAtXfJ4nm8TURPdqWY2ER9cfQxqPEF1jrfv5T4GmPE/GmFy4WJs+s13j8DKsWifOAxgGLRw==", 257 | "dev": true 258 | }, 259 | "@hapi/eslint-plugin-hapi": { 260 | "version": "4.3.5", 261 | "resolved": "https://registry.npmjs.org/@hapi/eslint-plugin-hapi/-/eslint-plugin-hapi-4.3.5.tgz", 262 | "integrity": "sha512-2o6eJrE6qH0orR0kDB3B+lyQ1Swq7SrZ+ZubvE5UGmXBrHOYmWV/RhuvCU0mmSwRn0vyU3sK41doEqya7G3lIA==", 263 | "dev": true, 264 | "requires": { 265 | "@hapi/rule-capitalize-modules": "1.x.x", 266 | "@hapi/rule-for-loop": "1.x.x", 267 | "@hapi/rule-no-arrowception": "1.x.x", 268 | "@hapi/rule-no-var": "1.x.x", 269 | "@hapi/rule-scope-start": "2.x.x" 270 | } 271 | }, 272 | "@hapi/file": { 273 | "version": "2.0.0", 274 | "resolved": "https://registry.npmjs.org/@hapi/file/-/file-2.0.0.tgz", 275 | "integrity": "sha512-WSrlgpvEqgPWkI18kkGELEZfXr0bYLtr16iIN4Krh9sRnzBZN6nnWxHFxtsnP684wueEySBbXPDg/WfA9xJdBQ==", 276 | "dev": true 277 | }, 278 | "@hapi/formula": { 279 | "version": "2.0.0", 280 | "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", 281 | "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==" 282 | }, 283 | "@hapi/hapi": { 284 | "version": "19.1.1", 285 | "resolved": "https://registry.npmjs.org/@hapi/hapi/-/hapi-19.1.1.tgz", 286 | "integrity": "sha512-rpQzSs0XsHSF7usM4qdJJ0Bcmhs9stWhUW3OiamW33bw4qL8q3uEgUKB9KH8ODmluCAkkXOQ0X0Dh9t94E5VIw==", 287 | "dev": true, 288 | "requires": { 289 | "@hapi/accept": "^5.0.1", 290 | "@hapi/ammo": "^5.0.1", 291 | "@hapi/boom": "9.x.x", 292 | "@hapi/bounce": "2.x.x", 293 | "@hapi/call": "8.x.x", 294 | "@hapi/catbox": "11.x.x", 295 | "@hapi/catbox-memory": "5.x.x", 296 | "@hapi/heavy": "7.x.x", 297 | "@hapi/hoek": "9.x.x", 298 | "@hapi/joi": "17.x.x", 299 | "@hapi/mimos": "5.x.x", 300 | "@hapi/podium": "4.x.x", 301 | "@hapi/shot": "5.x.x", 302 | "@hapi/somever": "3.x.x", 303 | "@hapi/statehood": "^7.0.2", 304 | "@hapi/subtext": "^7.0.3", 305 | "@hapi/teamwork": "4.x.x", 306 | "@hapi/topo": "5.x.x" 307 | } 308 | }, 309 | "@hapi/heavy": { 310 | "version": "7.0.0", 311 | "resolved": "https://registry.npmjs.org/@hapi/heavy/-/heavy-7.0.0.tgz", 312 | "integrity": "sha512-n/nheUG6zNleWkjY+3fzV3VJIAumUCaa/WoTmurjqlYY5JgC5ZKOpvP7tWi8rXmKZhbcXgjH3fHFoM55LoBT7g==", 313 | "dev": true, 314 | "requires": { 315 | "@hapi/boom": "9.x.x", 316 | "@hapi/hoek": "9.x.x", 317 | "@hapi/joi": "17.x.x" 318 | } 319 | }, 320 | "@hapi/hoek": { 321 | "version": "9.0.3", 322 | "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.3.tgz", 323 | "integrity": "sha512-jKtjLLDiH95b002sJVc5c74PE6KKYftuyVdVmsuYId5stTaWcRFqE+5ukZI4gDUKjGn8wv2C3zPn3/nyjEI7gg==" 324 | }, 325 | "@hapi/iron": { 326 | "version": "6.0.0", 327 | "resolved": "https://registry.npmjs.org/@hapi/iron/-/iron-6.0.0.tgz", 328 | "integrity": "sha512-zvGvWDufiTGpTJPG1Y/McN8UqWBu0k/xs/7l++HVU535NLHXsHhy54cfEMdW7EjwKfbBfM9Xy25FmTiobb7Hvw==", 329 | "dev": true, 330 | "requires": { 331 | "@hapi/b64": "5.x.x", 332 | "@hapi/boom": "9.x.x", 333 | "@hapi/bourne": "2.x.x", 334 | "@hapi/cryptiles": "5.x.x", 335 | "@hapi/hoek": "9.x.x" 336 | } 337 | }, 338 | "@hapi/joi": { 339 | "version": "17.1.0", 340 | "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.0.tgz", 341 | "integrity": "sha512-ob67RcPlwRWxBzLCnWvcwx5qbwf88I3ykD7gcJLWOTRfLLgosK7r6aeChz4thA3XRvuBfI0KB1tPVl2EQFlPXw==", 342 | "requires": { 343 | "@hapi/address": "^4.0.0", 344 | "@hapi/formula": "^2.0.0", 345 | "@hapi/hoek": "^9.0.0", 346 | "@hapi/pinpoint": "^2.0.0", 347 | "@hapi/topo": "^5.0.0" 348 | } 349 | }, 350 | "@hapi/lab": { 351 | "version": "22.0.3", 352 | "resolved": "https://registry.npmjs.org/@hapi/lab/-/lab-22.0.3.tgz", 353 | "integrity": "sha512-p7XJOlC+0Fk3tYiiEAfH2RJraCYDWgqw15YiwyVA2LzlHVUVn5JATjkhPvwIT+0hwb1Tc+y6o4Gvy5UYwLKUig==", 354 | "dev": true, 355 | "requires": { 356 | "@hapi/bossy": "5.x.x", 357 | "@hapi/eslint-config-hapi": "13.x.x", 358 | "@hapi/eslint-plugin-hapi": "4.x.x", 359 | "@hapi/hoek": "9.x.x", 360 | "babel-eslint": "10.x.x", 361 | "diff": "4.x.x", 362 | "eslint": "6.x.x", 363 | "find-rc": "4.x.x", 364 | "globby": "10.x.x", 365 | "handlebars": "4.x.x", 366 | "mkdirp": "0.5.x", 367 | "seedrandom": "3.x.x", 368 | "source-map": "0.7.x", 369 | "source-map-support": "0.5.x", 370 | "supports-color": "7.x.x", 371 | "typescript": "3.6.x", 372 | "will-call": "1.x.x" 373 | }, 374 | "dependencies": { 375 | "source-map": { 376 | "version": "0.7.3", 377 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", 378 | "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", 379 | "dev": true 380 | } 381 | } 382 | }, 383 | "@hapi/mimos": { 384 | "version": "5.0.0", 385 | "resolved": "https://registry.npmjs.org/@hapi/mimos/-/mimos-5.0.0.tgz", 386 | "integrity": "sha512-EVS6wJYeE73InTlPWt+2e3Izn319iIvffDreci3qDNT+t3lA5ylJ0/SoTaID8e0TPNUkHUSsgJZXEmLHvoYzrA==", 387 | "dev": true, 388 | "requires": { 389 | "@hapi/hoek": "9.x.x", 390 | "mime-db": "1.x.x" 391 | } 392 | }, 393 | "@hapi/nigel": { 394 | "version": "4.0.0", 395 | "resolved": "https://registry.npmjs.org/@hapi/nigel/-/nigel-4.0.0.tgz", 396 | "integrity": "sha512-Bqs1pjcDnDQo/XGoiCCNHWTFcMzPbz3L4KU04njeFQMzzEmsojMRX7TX+PezQYCMKtHJOtMg0bHxZyMGqYtbSA==", 397 | "dev": true, 398 | "requires": { 399 | "@hapi/hoek": "9.x.x", 400 | "@hapi/vise": "4.x.x" 401 | } 402 | }, 403 | "@hapi/pez": { 404 | "version": "5.0.2", 405 | "resolved": "https://registry.npmjs.org/@hapi/pez/-/pez-5.0.2.tgz", 406 | "integrity": "sha512-jr1lAm8mE7J2IBxvDIuDI1qy2aAsoaD2jxOUd/7JRg/Vmrzco8HdKhtz4fKk6KHU6zbbsAp5m5aSWWVTUrag7g==", 407 | "dev": true, 408 | "requires": { 409 | "@hapi/b64": "5.x.x", 410 | "@hapi/boom": "9.x.x", 411 | "@hapi/content": "^5.0.2", 412 | "@hapi/hoek": "9.x.x", 413 | "@hapi/nigel": "4.x.x" 414 | } 415 | }, 416 | "@hapi/pinpoint": { 417 | "version": "2.0.0", 418 | "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", 419 | "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==" 420 | }, 421 | "@hapi/podium": { 422 | "version": "4.0.0", 423 | "resolved": "https://registry.npmjs.org/@hapi/podium/-/podium-4.0.0.tgz", 424 | "integrity": "sha512-2T74rkS4OZjbsSdx4jIMbjb3cF/X6BpEE2kar71ascgL+9lf+5eczirlw3WWQ9ng2YDU469IVrADd6LYMzhEdw==", 425 | "dev": true, 426 | "requires": { 427 | "@hapi/hoek": "9.x.x", 428 | "@hapi/joi": "17.x.x" 429 | } 430 | }, 431 | "@hapi/rule-capitalize-modules": { 432 | "version": "1.2.1", 433 | "resolved": "https://registry.npmjs.org/@hapi/rule-capitalize-modules/-/rule-capitalize-modules-1.2.1.tgz", 434 | "integrity": "sha512-XduBSQQehgY/PJX/Ud2H5UdVyNVEC3QiU00vOHWvpn+kbH1co2dmzpu2JEGGxKmdGHjm8jdDkrlqVxGFXHAuhQ==", 435 | "dev": true 436 | }, 437 | "@hapi/rule-for-loop": { 438 | "version": "1.2.1", 439 | "resolved": "https://registry.npmjs.org/@hapi/rule-for-loop/-/rule-for-loop-1.2.1.tgz", 440 | "integrity": "sha512-9Y2yjNpmhntryViPTb6JlTCqma9fF+H0lCtjvlWA0La/ckxPSzXfwh9kgroyjQ3mJiwKDUYboqC4/BK6L5DFUg==", 441 | "dev": true 442 | }, 443 | "@hapi/rule-no-arrowception": { 444 | "version": "1.1.2", 445 | "resolved": "https://registry.npmjs.org/@hapi/rule-no-arrowception/-/rule-no-arrowception-1.1.2.tgz", 446 | "integrity": "sha512-NV6IpfcUpI6w/6oR2oBFaBUoOGC3j3xzfXq7ZciBnmOyReqwuSiyvwLb9SeSomei/n1LHaVdnIXJnMD9IAma2Q==", 447 | "dev": true 448 | }, 449 | "@hapi/rule-no-var": { 450 | "version": "1.1.4", 451 | "resolved": "https://registry.npmjs.org/@hapi/rule-no-var/-/rule-no-var-1.1.4.tgz", 452 | "integrity": "sha512-u0gtMRd9uxlmmew3H5pBZJe1D64dTz5yhPWU3UcurOwZGTbGYU2PAUpjxE0TOaeCRDW+tL5Y/9f7637P2vqQSA==", 453 | "dev": true 454 | }, 455 | "@hapi/rule-scope-start": { 456 | "version": "2.2.0", 457 | "resolved": "https://registry.npmjs.org/@hapi/rule-scope-start/-/rule-scope-start-2.2.0.tgz", 458 | "integrity": "sha512-n0adld0osaYNXlus/64dCN0GlkMvmwuJfkpM0OtrA2U7x2Iu1XoHV6Lmne3C+9gM8X/xLUviYLoTCOC7IW8RYg==", 459 | "dev": true 460 | }, 461 | "@hapi/scooter": { 462 | "version": "6.0.0", 463 | "resolved": "https://registry.npmjs.org/@hapi/scooter/-/scooter-6.0.0.tgz", 464 | "integrity": "sha512-OT8ehMkTcKmgvLGMUrWQWh5R+P0FFNwbenaLPLanvGW31LgrJWWLJNEs6Wf4mervw+k9lpeiSHbpPbk1wFls+g==", 465 | "dev": true, 466 | "requires": { 467 | "semver": "6.x.x", 468 | "useragent": "2.x.x" 469 | } 470 | }, 471 | "@hapi/shot": { 472 | "version": "5.0.0", 473 | "resolved": "https://registry.npmjs.org/@hapi/shot/-/shot-5.0.0.tgz", 474 | "integrity": "sha512-JXddnJkRh3Xhv9lY1tA+TSIUaoODKbdNIPL/M8WFvFQKOttmGaDeqTW5e8Gf01LtLI7L5DraLMULHjrK1+YNFg==", 475 | "dev": true, 476 | "requires": { 477 | "@hapi/hoek": "9.x.x", 478 | "@hapi/joi": "17.x.x" 479 | } 480 | }, 481 | "@hapi/somever": { 482 | "version": "3.0.0", 483 | "resolved": "https://registry.npmjs.org/@hapi/somever/-/somever-3.0.0.tgz", 484 | "integrity": "sha512-Upw/kmKotC9iEmK4y047HMYe4LDKsE5NWfjgX41XNKmFvxsQL7OiaCWVhuyyhU0ShDGBfIAnCH8jZr49z/JzZA==", 485 | "dev": true, 486 | "requires": { 487 | "@hapi/bounce": "2.x.x", 488 | "@hapi/hoek": "9.x.x" 489 | } 490 | }, 491 | "@hapi/statehood": { 492 | "version": "7.0.2", 493 | "resolved": "https://registry.npmjs.org/@hapi/statehood/-/statehood-7.0.2.tgz", 494 | "integrity": "sha512-+0VNxysQu+UYzkfvAXq3X4aN65TnUwiR7gsq2cQ/4Rq26nCJjHAfrkYReEeshU2hPmJ3m5QuaBzyDqRm8WOpyg==", 495 | "dev": true, 496 | "requires": { 497 | "@hapi/boom": "9.x.x", 498 | "@hapi/bounce": "2.x.x", 499 | "@hapi/bourne": "2.x.x", 500 | "@hapi/cryptiles": "5.x.x", 501 | "@hapi/hoek": "9.x.x", 502 | "@hapi/iron": "6.x.x", 503 | "@hapi/joi": "17.x.x" 504 | } 505 | }, 506 | "@hapi/subtext": { 507 | "version": "7.0.3", 508 | "resolved": "https://registry.npmjs.org/@hapi/subtext/-/subtext-7.0.3.tgz", 509 | "integrity": "sha512-CekDizZkDGERJ01C0+TzHlKtqdXZxzSWTOaH6THBrbOHnsr3GY+yiMZC+AfNCypfE17RaIakGIAbpL2Tk1z2+A==", 510 | "dev": true, 511 | "requires": { 512 | "@hapi/boom": "9.x.x", 513 | "@hapi/bourne": "2.x.x", 514 | "@hapi/content": "^5.0.2", 515 | "@hapi/file": "2.x.x", 516 | "@hapi/hoek": "9.x.x", 517 | "@hapi/pez": "^5.0.1", 518 | "@hapi/wreck": "17.x.x" 519 | } 520 | }, 521 | "@hapi/teamwork": { 522 | "version": "4.0.0", 523 | "resolved": "https://registry.npmjs.org/@hapi/teamwork/-/teamwork-4.0.0.tgz", 524 | "integrity": "sha512-V6xYOrr5aFv/IJqNPneaYCu8vuGTKisamqHVRS3JJnbZr18TrpXdsJOYk9pjPhFti+M2YETPebQLUr820N5NoQ==", 525 | "dev": true 526 | }, 527 | "@hapi/topo": { 528 | "version": "5.0.0", 529 | "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", 530 | "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", 531 | "requires": { 532 | "@hapi/hoek": "^9.0.0" 533 | } 534 | }, 535 | "@hapi/vise": { 536 | "version": "4.0.0", 537 | "resolved": "https://registry.npmjs.org/@hapi/vise/-/vise-4.0.0.tgz", 538 | "integrity": "sha512-eYyLkuUiFZTer59h+SGy7hUm+qE9p+UemePTHLlIWppEd+wExn3Df5jO04bFQTm7nleF5V8CtuYQYb+VFpZ6Sg==", 539 | "dev": true, 540 | "requires": { 541 | "@hapi/hoek": "9.x.x" 542 | } 543 | }, 544 | "@hapi/vision": { 545 | "version": "6.0.0", 546 | "resolved": "https://registry.npmjs.org/@hapi/vision/-/vision-6.0.0.tgz", 547 | "integrity": "sha512-NRG87SLS8evCTwAGlVHykmlk1i9z+7TD2pUwoDHRpjwqX/syt0ecrEKMLV/VdMijE9tCAtKIIXt+Myb16tT7Sw==", 548 | "dev": true, 549 | "requires": { 550 | "@hapi/boom": "9.x.x", 551 | "@hapi/bounce": "2.x.x", 552 | "@hapi/hoek": "9.x.x", 553 | "@hapi/joi": "17.x.x" 554 | } 555 | }, 556 | "@hapi/wreck": { 557 | "version": "17.0.0", 558 | "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-17.0.0.tgz", 559 | "integrity": "sha512-d8lqCinbKyDByn7GzJDRDbitddhIEydNm44UcAMejfhEH3o4IYvKYq6K8cAqXbilXPuvZc0ErlUOg9SDdgRtMw==", 560 | "dev": true, 561 | "requires": { 562 | "@hapi/boom": "9.x.x", 563 | "@hapi/bourne": "2.x.x", 564 | "@hapi/hoek": "9.x.x" 565 | } 566 | }, 567 | "@nodelib/fs.scandir": { 568 | "version": "2.1.3", 569 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", 570 | "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", 571 | "dev": true, 572 | "requires": { 573 | "@nodelib/fs.stat": "2.0.3", 574 | "run-parallel": "^1.1.9" 575 | } 576 | }, 577 | "@nodelib/fs.stat": { 578 | "version": "2.0.3", 579 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", 580 | "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", 581 | "dev": true 582 | }, 583 | "@nodelib/fs.walk": { 584 | "version": "1.2.4", 585 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", 586 | "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", 587 | "dev": true, 588 | "requires": { 589 | "@nodelib/fs.scandir": "2.1.3", 590 | "fastq": "^1.6.0" 591 | } 592 | }, 593 | "@types/events": { 594 | "version": "3.0.0", 595 | "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", 596 | "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", 597 | "dev": true 598 | }, 599 | "@types/glob": { 600 | "version": "7.1.1", 601 | "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", 602 | "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", 603 | "dev": true, 604 | "requires": { 605 | "@types/events": "*", 606 | "@types/minimatch": "*", 607 | "@types/node": "*" 608 | } 609 | }, 610 | "@types/minimatch": { 611 | "version": "3.0.3", 612 | "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", 613 | "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", 614 | "dev": true 615 | }, 616 | "@types/node": { 617 | "version": "13.7.1", 618 | "resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.1.tgz", 619 | "integrity": "sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA==", 620 | "dev": true 621 | }, 622 | "acorn": { 623 | "version": "7.1.1", 624 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", 625 | "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", 626 | "dev": true 627 | }, 628 | "acorn-jsx": { 629 | "version": "5.1.0", 630 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", 631 | "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", 632 | "dev": true 633 | }, 634 | "ajv": { 635 | "version": "6.11.0", 636 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", 637 | "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", 638 | "dev": true, 639 | "requires": { 640 | "fast-deep-equal": "^3.1.1", 641 | "fast-json-stable-stringify": "^2.0.0", 642 | "json-schema-traverse": "^0.4.1", 643 | "uri-js": "^4.2.2" 644 | } 645 | }, 646 | "ansi-escapes": { 647 | "version": "4.3.0", 648 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", 649 | "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", 650 | "dev": true, 651 | "requires": { 652 | "type-fest": "^0.8.1" 653 | } 654 | }, 655 | "ansi-regex": { 656 | "version": "5.0.0", 657 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 658 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", 659 | "dev": true 660 | }, 661 | "ansi-styles": { 662 | "version": "3.2.1", 663 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 664 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 665 | "dev": true, 666 | "requires": { 667 | "color-convert": "^1.9.0" 668 | } 669 | }, 670 | "argparse": { 671 | "version": "1.0.10", 672 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 673 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 674 | "dev": true, 675 | "requires": { 676 | "sprintf-js": "~1.0.2" 677 | } 678 | }, 679 | "array-union": { 680 | "version": "2.1.0", 681 | "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", 682 | "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", 683 | "dev": true 684 | }, 685 | "astral-regex": { 686 | "version": "1.0.0", 687 | "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", 688 | "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", 689 | "dev": true 690 | }, 691 | "babel-eslint": { 692 | "version": "10.0.3", 693 | "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.3.tgz", 694 | "integrity": "sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==", 695 | "dev": true, 696 | "requires": { 697 | "@babel/code-frame": "^7.0.0", 698 | "@babel/parser": "^7.0.0", 699 | "@babel/traverse": "^7.0.0", 700 | "@babel/types": "^7.0.0", 701 | "eslint-visitor-keys": "^1.0.0", 702 | "resolve": "^1.12.0" 703 | } 704 | }, 705 | "balanced-match": { 706 | "version": "1.0.0", 707 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 708 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 709 | "dev": true 710 | }, 711 | "brace-expansion": { 712 | "version": "1.1.11", 713 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 714 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 715 | "dev": true, 716 | "requires": { 717 | "balanced-match": "^1.0.0", 718 | "concat-map": "0.0.1" 719 | } 720 | }, 721 | "braces": { 722 | "version": "3.0.2", 723 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 724 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 725 | "dev": true, 726 | "requires": { 727 | "fill-range": "^7.0.1" 728 | } 729 | }, 730 | "browser-agents": { 731 | "version": "1.0.1", 732 | "resolved": "https://registry.npmjs.org/browser-agents/-/browser-agents-1.0.1.tgz", 733 | "integrity": "sha1-h4Y+4ObJgw9Q1xWl1ZCpW/orIXo=", 734 | "dev": true 735 | }, 736 | "buffer-from": { 737 | "version": "1.1.1", 738 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 739 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 740 | "dev": true 741 | }, 742 | "callsites": { 743 | "version": "3.1.0", 744 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 745 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 746 | "dev": true 747 | }, 748 | "chalk": { 749 | "version": "2.4.2", 750 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 751 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 752 | "dev": true, 753 | "requires": { 754 | "ansi-styles": "^3.2.1", 755 | "escape-string-regexp": "^1.0.5", 756 | "supports-color": "^5.3.0" 757 | }, 758 | "dependencies": { 759 | "supports-color": { 760 | "version": "5.5.0", 761 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 762 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 763 | "dev": true, 764 | "requires": { 765 | "has-flag": "^3.0.0" 766 | } 767 | } 768 | } 769 | }, 770 | "chardet": { 771 | "version": "0.7.0", 772 | "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", 773 | "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", 774 | "dev": true 775 | }, 776 | "cli-cursor": { 777 | "version": "3.1.0", 778 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", 779 | "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", 780 | "dev": true, 781 | "requires": { 782 | "restore-cursor": "^3.1.0" 783 | } 784 | }, 785 | "cli-width": { 786 | "version": "2.2.0", 787 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", 788 | "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", 789 | "dev": true 790 | }, 791 | "color-convert": { 792 | "version": "1.9.3", 793 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 794 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 795 | "dev": true, 796 | "requires": { 797 | "color-name": "1.1.3" 798 | } 799 | }, 800 | "color-name": { 801 | "version": "1.1.3", 802 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 803 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 804 | "dev": true 805 | }, 806 | "commander": { 807 | "version": "2.20.3", 808 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 809 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 810 | "dev": true, 811 | "optional": true 812 | }, 813 | "concat-map": { 814 | "version": "0.0.1", 815 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 816 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 817 | "dev": true 818 | }, 819 | "cross-spawn": { 820 | "version": "6.0.5", 821 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 822 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 823 | "dev": true, 824 | "requires": { 825 | "nice-try": "^1.0.4", 826 | "path-key": "^2.0.1", 827 | "semver": "^5.5.0", 828 | "shebang-command": "^1.2.0", 829 | "which": "^1.2.9" 830 | }, 831 | "dependencies": { 832 | "semver": { 833 | "version": "5.7.1", 834 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 835 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 836 | "dev": true 837 | } 838 | } 839 | }, 840 | "debug": { 841 | "version": "4.1.1", 842 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 843 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 844 | "dev": true, 845 | "requires": { 846 | "ms": "^2.1.1" 847 | } 848 | }, 849 | "deep-is": { 850 | "version": "0.1.3", 851 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", 852 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", 853 | "dev": true 854 | }, 855 | "diff": { 856 | "version": "4.0.2", 857 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 858 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 859 | "dev": true 860 | }, 861 | "dir-glob": { 862 | "version": "3.0.1", 863 | "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", 864 | "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", 865 | "dev": true, 866 | "requires": { 867 | "path-type": "^4.0.0" 868 | } 869 | }, 870 | "doctrine": { 871 | "version": "3.0.0", 872 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", 873 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", 874 | "dev": true, 875 | "requires": { 876 | "esutils": "^2.0.2" 877 | } 878 | }, 879 | "emoji-regex": { 880 | "version": "8.0.0", 881 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 882 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 883 | "dev": true 884 | }, 885 | "escape-string-regexp": { 886 | "version": "1.0.5", 887 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 888 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 889 | "dev": true 890 | }, 891 | "eslint": { 892 | "version": "6.8.0", 893 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", 894 | "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", 895 | "dev": true, 896 | "requires": { 897 | "@babel/code-frame": "^7.0.0", 898 | "ajv": "^6.10.0", 899 | "chalk": "^2.1.0", 900 | "cross-spawn": "^6.0.5", 901 | "debug": "^4.0.1", 902 | "doctrine": "^3.0.0", 903 | "eslint-scope": "^5.0.0", 904 | "eslint-utils": "^1.4.3", 905 | "eslint-visitor-keys": "^1.1.0", 906 | "espree": "^6.1.2", 907 | "esquery": "^1.0.1", 908 | "esutils": "^2.0.2", 909 | "file-entry-cache": "^5.0.1", 910 | "functional-red-black-tree": "^1.0.1", 911 | "glob-parent": "^5.0.0", 912 | "globals": "^12.1.0", 913 | "ignore": "^4.0.6", 914 | "import-fresh": "^3.0.0", 915 | "imurmurhash": "^0.1.4", 916 | "inquirer": "^7.0.0", 917 | "is-glob": "^4.0.0", 918 | "js-yaml": "^3.13.1", 919 | "json-stable-stringify-without-jsonify": "^1.0.1", 920 | "levn": "^0.3.0", 921 | "lodash": "^4.17.14", 922 | "minimatch": "^3.0.4", 923 | "mkdirp": "^0.5.1", 924 | "natural-compare": "^1.4.0", 925 | "optionator": "^0.8.3", 926 | "progress": "^2.0.0", 927 | "regexpp": "^2.0.1", 928 | "semver": "^6.1.2", 929 | "strip-ansi": "^5.2.0", 930 | "strip-json-comments": "^3.0.1", 931 | "table": "^5.2.3", 932 | "text-table": "^0.2.0", 933 | "v8-compile-cache": "^2.0.3" 934 | }, 935 | "dependencies": { 936 | "globals": { 937 | "version": "12.3.0", 938 | "resolved": "https://registry.npmjs.org/globals/-/globals-12.3.0.tgz", 939 | "integrity": "sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==", 940 | "dev": true, 941 | "requires": { 942 | "type-fest": "^0.8.1" 943 | } 944 | } 945 | } 946 | }, 947 | "eslint-scope": { 948 | "version": "5.0.0", 949 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", 950 | "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", 951 | "dev": true, 952 | "requires": { 953 | "esrecurse": "^4.1.0", 954 | "estraverse": "^4.1.1" 955 | } 956 | }, 957 | "eslint-utils": { 958 | "version": "1.4.3", 959 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", 960 | "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", 961 | "dev": true, 962 | "requires": { 963 | "eslint-visitor-keys": "^1.1.0" 964 | } 965 | }, 966 | "eslint-visitor-keys": { 967 | "version": "1.1.0", 968 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", 969 | "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", 970 | "dev": true 971 | }, 972 | "espree": { 973 | "version": "6.1.2", 974 | "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", 975 | "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", 976 | "dev": true, 977 | "requires": { 978 | "acorn": "^7.1.0", 979 | "acorn-jsx": "^5.1.0", 980 | "eslint-visitor-keys": "^1.1.0" 981 | } 982 | }, 983 | "esprima": { 984 | "version": "4.0.1", 985 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 986 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 987 | "dev": true 988 | }, 989 | "esquery": { 990 | "version": "1.0.1", 991 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", 992 | "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", 993 | "dev": true, 994 | "requires": { 995 | "estraverse": "^4.0.0" 996 | } 997 | }, 998 | "esrecurse": { 999 | "version": "4.2.1", 1000 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", 1001 | "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", 1002 | "dev": true, 1003 | "requires": { 1004 | "estraverse": "^4.1.0" 1005 | } 1006 | }, 1007 | "estraverse": { 1008 | "version": "4.3.0", 1009 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", 1010 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", 1011 | "dev": true 1012 | }, 1013 | "esutils": { 1014 | "version": "2.0.3", 1015 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 1016 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 1017 | "dev": true 1018 | }, 1019 | "external-editor": { 1020 | "version": "3.1.0", 1021 | "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", 1022 | "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", 1023 | "dev": true, 1024 | "requires": { 1025 | "chardet": "^0.7.0", 1026 | "iconv-lite": "^0.4.24", 1027 | "tmp": "^0.0.33" 1028 | } 1029 | }, 1030 | "fast-deep-equal": { 1031 | "version": "3.1.1", 1032 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", 1033 | "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", 1034 | "dev": true 1035 | }, 1036 | "fast-glob": { 1037 | "version": "3.1.1", 1038 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.1.tgz", 1039 | "integrity": "sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g==", 1040 | "dev": true, 1041 | "requires": { 1042 | "@nodelib/fs.stat": "^2.0.2", 1043 | "@nodelib/fs.walk": "^1.2.3", 1044 | "glob-parent": "^5.1.0", 1045 | "merge2": "^1.3.0", 1046 | "micromatch": "^4.0.2" 1047 | } 1048 | }, 1049 | "fast-json-stable-stringify": { 1050 | "version": "2.1.0", 1051 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 1052 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 1053 | "dev": true 1054 | }, 1055 | "fast-levenshtein": { 1056 | "version": "2.0.6", 1057 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 1058 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", 1059 | "dev": true 1060 | }, 1061 | "fastq": { 1062 | "version": "1.6.0", 1063 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz", 1064 | "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==", 1065 | "dev": true, 1066 | "requires": { 1067 | "reusify": "^1.0.0" 1068 | } 1069 | }, 1070 | "figures": { 1071 | "version": "3.1.0", 1072 | "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz", 1073 | "integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==", 1074 | "dev": true, 1075 | "requires": { 1076 | "escape-string-regexp": "^1.0.5" 1077 | } 1078 | }, 1079 | "file-entry-cache": { 1080 | "version": "5.0.1", 1081 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", 1082 | "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", 1083 | "dev": true, 1084 | "requires": { 1085 | "flat-cache": "^2.0.1" 1086 | } 1087 | }, 1088 | "fill-range": { 1089 | "version": "7.0.1", 1090 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 1091 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 1092 | "dev": true, 1093 | "requires": { 1094 | "to-regex-range": "^5.0.1" 1095 | } 1096 | }, 1097 | "find-rc": { 1098 | "version": "4.0.0", 1099 | "resolved": "https://registry.npmjs.org/find-rc/-/find-rc-4.0.0.tgz", 1100 | "integrity": "sha512-jvkAF340j/ntR8cBRPLg/ElqWodgjfInY4SwLqDVqrmYDJormOIfM4lbtIcLZ0x8W5xWyrUy+mdoMwyo6OYuaQ==", 1101 | "dev": true 1102 | }, 1103 | "flat-cache": { 1104 | "version": "2.0.1", 1105 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", 1106 | "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", 1107 | "dev": true, 1108 | "requires": { 1109 | "flatted": "^2.0.0", 1110 | "rimraf": "2.6.3", 1111 | "write": "1.0.3" 1112 | } 1113 | }, 1114 | "flatted": { 1115 | "version": "2.0.1", 1116 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", 1117 | "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", 1118 | "dev": true 1119 | }, 1120 | "fs.realpath": { 1121 | "version": "1.0.0", 1122 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 1123 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 1124 | "dev": true 1125 | }, 1126 | "functional-red-black-tree": { 1127 | "version": "1.0.1", 1128 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", 1129 | "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", 1130 | "dev": true 1131 | }, 1132 | "glob": { 1133 | "version": "7.1.6", 1134 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 1135 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 1136 | "dev": true, 1137 | "requires": { 1138 | "fs.realpath": "^1.0.0", 1139 | "inflight": "^1.0.4", 1140 | "inherits": "2", 1141 | "minimatch": "^3.0.4", 1142 | "once": "^1.3.0", 1143 | "path-is-absolute": "^1.0.0" 1144 | } 1145 | }, 1146 | "glob-parent": { 1147 | "version": "5.1.0", 1148 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", 1149 | "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", 1150 | "dev": true, 1151 | "requires": { 1152 | "is-glob": "^4.0.1" 1153 | } 1154 | }, 1155 | "globals": { 1156 | "version": "11.12.0", 1157 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 1158 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", 1159 | "dev": true 1160 | }, 1161 | "globby": { 1162 | "version": "10.0.2", 1163 | "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", 1164 | "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", 1165 | "dev": true, 1166 | "requires": { 1167 | "@types/glob": "^7.1.1", 1168 | "array-union": "^2.1.0", 1169 | "dir-glob": "^3.0.1", 1170 | "fast-glob": "^3.0.3", 1171 | "glob": "^7.1.3", 1172 | "ignore": "^5.1.1", 1173 | "merge2": "^1.2.3", 1174 | "slash": "^3.0.0" 1175 | }, 1176 | "dependencies": { 1177 | "ignore": { 1178 | "version": "5.1.4", 1179 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", 1180 | "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", 1181 | "dev": true 1182 | } 1183 | } 1184 | }, 1185 | "handlebars": { 1186 | "version": "4.7.3", 1187 | "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.3.tgz", 1188 | "integrity": "sha512-SRGwSYuNfx8DwHD/6InAPzD6RgeruWLT+B8e8a7gGs8FWgHzlExpTFMEq2IA6QpAfOClpKHy6+8IqTjeBCu6Kg==", 1189 | "dev": true, 1190 | "requires": { 1191 | "neo-async": "^2.6.0", 1192 | "optimist": "^0.6.1", 1193 | "source-map": "^0.6.1", 1194 | "uglify-js": "^3.1.4" 1195 | } 1196 | }, 1197 | "has-flag": { 1198 | "version": "3.0.0", 1199 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 1200 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 1201 | "dev": true 1202 | }, 1203 | "iconv-lite": { 1204 | "version": "0.4.24", 1205 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 1206 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 1207 | "dev": true, 1208 | "requires": { 1209 | "safer-buffer": ">= 2.1.2 < 3" 1210 | } 1211 | }, 1212 | "ignore": { 1213 | "version": "4.0.6", 1214 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", 1215 | "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", 1216 | "dev": true 1217 | }, 1218 | "import-fresh": { 1219 | "version": "3.2.1", 1220 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", 1221 | "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", 1222 | "dev": true, 1223 | "requires": { 1224 | "parent-module": "^1.0.0", 1225 | "resolve-from": "^4.0.0" 1226 | } 1227 | }, 1228 | "imurmurhash": { 1229 | "version": "0.1.4", 1230 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 1231 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", 1232 | "dev": true 1233 | }, 1234 | "inflight": { 1235 | "version": "1.0.6", 1236 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1237 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 1238 | "dev": true, 1239 | "requires": { 1240 | "once": "^1.3.0", 1241 | "wrappy": "1" 1242 | } 1243 | }, 1244 | "inherits": { 1245 | "version": "2.0.4", 1246 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1247 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1248 | "dev": true 1249 | }, 1250 | "inquirer": { 1251 | "version": "7.0.4", 1252 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz", 1253 | "integrity": "sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==", 1254 | "dev": true, 1255 | "requires": { 1256 | "ansi-escapes": "^4.2.1", 1257 | "chalk": "^2.4.2", 1258 | "cli-cursor": "^3.1.0", 1259 | "cli-width": "^2.0.0", 1260 | "external-editor": "^3.0.3", 1261 | "figures": "^3.0.0", 1262 | "lodash": "^4.17.15", 1263 | "mute-stream": "0.0.8", 1264 | "run-async": "^2.2.0", 1265 | "rxjs": "^6.5.3", 1266 | "string-width": "^4.1.0", 1267 | "strip-ansi": "^5.1.0", 1268 | "through": "^2.3.6" 1269 | } 1270 | }, 1271 | "is-extglob": { 1272 | "version": "2.1.1", 1273 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1274 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 1275 | "dev": true 1276 | }, 1277 | "is-fullwidth-code-point": { 1278 | "version": "3.0.0", 1279 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 1280 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 1281 | "dev": true 1282 | }, 1283 | "is-glob": { 1284 | "version": "4.0.1", 1285 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", 1286 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", 1287 | "dev": true, 1288 | "requires": { 1289 | "is-extglob": "^2.1.1" 1290 | } 1291 | }, 1292 | "is-number": { 1293 | "version": "7.0.0", 1294 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1295 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1296 | "dev": true 1297 | }, 1298 | "is-promise": { 1299 | "version": "2.1.0", 1300 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", 1301 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", 1302 | "dev": true 1303 | }, 1304 | "isexe": { 1305 | "version": "2.0.0", 1306 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1307 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 1308 | "dev": true 1309 | }, 1310 | "js-tokens": { 1311 | "version": "4.0.0", 1312 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1313 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 1314 | "dev": true 1315 | }, 1316 | "js-yaml": { 1317 | "version": "3.13.1", 1318 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", 1319 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", 1320 | "dev": true, 1321 | "requires": { 1322 | "argparse": "^1.0.7", 1323 | "esprima": "^4.0.0" 1324 | } 1325 | }, 1326 | "jsesc": { 1327 | "version": "2.5.2", 1328 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", 1329 | "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", 1330 | "dev": true 1331 | }, 1332 | "json-schema-traverse": { 1333 | "version": "0.4.1", 1334 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 1335 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 1336 | "dev": true 1337 | }, 1338 | "json-stable-stringify-without-jsonify": { 1339 | "version": "1.0.1", 1340 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 1341 | "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", 1342 | "dev": true 1343 | }, 1344 | "levn": { 1345 | "version": "0.3.0", 1346 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", 1347 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", 1348 | "dev": true, 1349 | "requires": { 1350 | "prelude-ls": "~1.1.2", 1351 | "type-check": "~0.3.2" 1352 | } 1353 | }, 1354 | "lodash": { 1355 | "version": "4.17.19", 1356 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", 1357 | "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", 1358 | "dev": true 1359 | }, 1360 | "lru-cache": { 1361 | "version": "4.1.5", 1362 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", 1363 | "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", 1364 | "dev": true, 1365 | "requires": { 1366 | "pseudomap": "^1.0.2", 1367 | "yallist": "^2.1.2" 1368 | } 1369 | }, 1370 | "merge2": { 1371 | "version": "1.3.0", 1372 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", 1373 | "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", 1374 | "dev": true 1375 | }, 1376 | "micromatch": { 1377 | "version": "4.0.2", 1378 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", 1379 | "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", 1380 | "dev": true, 1381 | "requires": { 1382 | "braces": "^3.0.1", 1383 | "picomatch": "^2.0.5" 1384 | } 1385 | }, 1386 | "mime-db": { 1387 | "version": "1.43.0", 1388 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", 1389 | "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", 1390 | "dev": true 1391 | }, 1392 | "mimic-fn": { 1393 | "version": "2.1.0", 1394 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", 1395 | "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", 1396 | "dev": true 1397 | }, 1398 | "minimatch": { 1399 | "version": "3.0.4", 1400 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1401 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1402 | "dev": true, 1403 | "requires": { 1404 | "brace-expansion": "^1.1.7" 1405 | } 1406 | }, 1407 | "minimist": { 1408 | "version": "0.0.10", 1409 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", 1410 | "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", 1411 | "dev": true 1412 | }, 1413 | "mkdirp": { 1414 | "version": "0.5.1", 1415 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 1416 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 1417 | "dev": true, 1418 | "requires": { 1419 | "minimist": "0.0.8" 1420 | }, 1421 | "dependencies": { 1422 | "minimist": { 1423 | "version": "0.0.8", 1424 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 1425 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 1426 | "dev": true 1427 | } 1428 | } 1429 | }, 1430 | "ms": { 1431 | "version": "2.1.2", 1432 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1433 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1434 | "dev": true 1435 | }, 1436 | "mute-stream": { 1437 | "version": "0.0.8", 1438 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", 1439 | "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", 1440 | "dev": true 1441 | }, 1442 | "natural-compare": { 1443 | "version": "1.4.0", 1444 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 1445 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", 1446 | "dev": true 1447 | }, 1448 | "neo-async": { 1449 | "version": "2.6.1", 1450 | "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", 1451 | "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", 1452 | "dev": true 1453 | }, 1454 | "nice-try": { 1455 | "version": "1.0.5", 1456 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 1457 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", 1458 | "dev": true 1459 | }, 1460 | "once": { 1461 | "version": "1.4.0", 1462 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1463 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1464 | "dev": true, 1465 | "requires": { 1466 | "wrappy": "1" 1467 | } 1468 | }, 1469 | "onetime": { 1470 | "version": "5.1.0", 1471 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", 1472 | "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", 1473 | "dev": true, 1474 | "requires": { 1475 | "mimic-fn": "^2.1.0" 1476 | } 1477 | }, 1478 | "optimist": { 1479 | "version": "0.6.1", 1480 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", 1481 | "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", 1482 | "dev": true, 1483 | "requires": { 1484 | "minimist": "~0.0.1", 1485 | "wordwrap": "~0.0.2" 1486 | } 1487 | }, 1488 | "optionator": { 1489 | "version": "0.8.3", 1490 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", 1491 | "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", 1492 | "dev": true, 1493 | "requires": { 1494 | "deep-is": "~0.1.3", 1495 | "fast-levenshtein": "~2.0.6", 1496 | "levn": "~0.3.0", 1497 | "prelude-ls": "~1.1.2", 1498 | "type-check": "~0.3.2", 1499 | "word-wrap": "~1.2.3" 1500 | } 1501 | }, 1502 | "os-tmpdir": { 1503 | "version": "1.0.2", 1504 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 1505 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", 1506 | "dev": true 1507 | }, 1508 | "parent-module": { 1509 | "version": "1.0.1", 1510 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 1511 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 1512 | "dev": true, 1513 | "requires": { 1514 | "callsites": "^3.0.0" 1515 | } 1516 | }, 1517 | "path-is-absolute": { 1518 | "version": "1.0.1", 1519 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1520 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1521 | "dev": true 1522 | }, 1523 | "path-key": { 1524 | "version": "2.0.1", 1525 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 1526 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", 1527 | "dev": true 1528 | }, 1529 | "path-parse": { 1530 | "version": "1.0.6", 1531 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 1532 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 1533 | "dev": true 1534 | }, 1535 | "path-type": { 1536 | "version": "4.0.0", 1537 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", 1538 | "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", 1539 | "dev": true 1540 | }, 1541 | "picomatch": { 1542 | "version": "2.2.1", 1543 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", 1544 | "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", 1545 | "dev": true 1546 | }, 1547 | "prelude-ls": { 1548 | "version": "1.1.2", 1549 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", 1550 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", 1551 | "dev": true 1552 | }, 1553 | "progress": { 1554 | "version": "2.0.3", 1555 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", 1556 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", 1557 | "dev": true 1558 | }, 1559 | "pseudomap": { 1560 | "version": "1.0.2", 1561 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", 1562 | "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", 1563 | "dev": true 1564 | }, 1565 | "punycode": { 1566 | "version": "2.1.1", 1567 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1568 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 1569 | "dev": true 1570 | }, 1571 | "regexpp": { 1572 | "version": "2.0.1", 1573 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", 1574 | "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", 1575 | "dev": true 1576 | }, 1577 | "resolve": { 1578 | "version": "1.15.1", 1579 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", 1580 | "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", 1581 | "dev": true, 1582 | "requires": { 1583 | "path-parse": "^1.0.6" 1584 | } 1585 | }, 1586 | "resolve-from": { 1587 | "version": "4.0.0", 1588 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 1589 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 1590 | "dev": true 1591 | }, 1592 | "restore-cursor": { 1593 | "version": "3.1.0", 1594 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", 1595 | "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", 1596 | "dev": true, 1597 | "requires": { 1598 | "onetime": "^5.1.0", 1599 | "signal-exit": "^3.0.2" 1600 | } 1601 | }, 1602 | "reusify": { 1603 | "version": "1.0.4", 1604 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 1605 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 1606 | "dev": true 1607 | }, 1608 | "rimraf": { 1609 | "version": "2.6.3", 1610 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", 1611 | "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", 1612 | "dev": true, 1613 | "requires": { 1614 | "glob": "^7.1.3" 1615 | } 1616 | }, 1617 | "run-async": { 1618 | "version": "2.3.0", 1619 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", 1620 | "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", 1621 | "dev": true, 1622 | "requires": { 1623 | "is-promise": "^2.1.0" 1624 | } 1625 | }, 1626 | "run-parallel": { 1627 | "version": "1.1.9", 1628 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", 1629 | "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", 1630 | "dev": true 1631 | }, 1632 | "rxjs": { 1633 | "version": "6.5.4", 1634 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", 1635 | "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", 1636 | "dev": true, 1637 | "requires": { 1638 | "tslib": "^1.9.0" 1639 | } 1640 | }, 1641 | "safer-buffer": { 1642 | "version": "2.1.2", 1643 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1644 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1645 | "dev": true 1646 | }, 1647 | "seedrandom": { 1648 | "version": "3.0.5", 1649 | "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", 1650 | "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", 1651 | "dev": true 1652 | }, 1653 | "semver": { 1654 | "version": "6.3.0", 1655 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", 1656 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", 1657 | "dev": true 1658 | }, 1659 | "shebang-command": { 1660 | "version": "1.2.0", 1661 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 1662 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 1663 | "dev": true, 1664 | "requires": { 1665 | "shebang-regex": "^1.0.0" 1666 | } 1667 | }, 1668 | "shebang-regex": { 1669 | "version": "1.0.0", 1670 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 1671 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", 1672 | "dev": true 1673 | }, 1674 | "signal-exit": { 1675 | "version": "3.0.2", 1676 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 1677 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", 1678 | "dev": true 1679 | }, 1680 | "slash": { 1681 | "version": "3.0.0", 1682 | "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", 1683 | "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", 1684 | "dev": true 1685 | }, 1686 | "slice-ansi": { 1687 | "version": "2.1.0", 1688 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", 1689 | "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", 1690 | "dev": true, 1691 | "requires": { 1692 | "ansi-styles": "^3.2.0", 1693 | "astral-regex": "^1.0.0", 1694 | "is-fullwidth-code-point": "^2.0.0" 1695 | }, 1696 | "dependencies": { 1697 | "is-fullwidth-code-point": { 1698 | "version": "2.0.0", 1699 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 1700 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 1701 | "dev": true 1702 | } 1703 | } 1704 | }, 1705 | "source-map": { 1706 | "version": "0.6.1", 1707 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 1708 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 1709 | "dev": true 1710 | }, 1711 | "source-map-support": { 1712 | "version": "0.5.16", 1713 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", 1714 | "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", 1715 | "dev": true, 1716 | "requires": { 1717 | "buffer-from": "^1.0.0", 1718 | "source-map": "^0.6.0" 1719 | } 1720 | }, 1721 | "sprintf-js": { 1722 | "version": "1.0.3", 1723 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 1724 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 1725 | "dev": true 1726 | }, 1727 | "string-width": { 1728 | "version": "4.2.0", 1729 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", 1730 | "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", 1731 | "dev": true, 1732 | "requires": { 1733 | "emoji-regex": "^8.0.0", 1734 | "is-fullwidth-code-point": "^3.0.0", 1735 | "strip-ansi": "^6.0.0" 1736 | }, 1737 | "dependencies": { 1738 | "strip-ansi": { 1739 | "version": "6.0.0", 1740 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 1741 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 1742 | "dev": true, 1743 | "requires": { 1744 | "ansi-regex": "^5.0.0" 1745 | } 1746 | } 1747 | } 1748 | }, 1749 | "strip-ansi": { 1750 | "version": "5.2.0", 1751 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 1752 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 1753 | "dev": true, 1754 | "requires": { 1755 | "ansi-regex": "^4.1.0" 1756 | }, 1757 | "dependencies": { 1758 | "ansi-regex": { 1759 | "version": "4.1.0", 1760 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 1761 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", 1762 | "dev": true 1763 | } 1764 | } 1765 | }, 1766 | "strip-json-comments": { 1767 | "version": "3.0.1", 1768 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", 1769 | "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", 1770 | "dev": true 1771 | }, 1772 | "supports-color": { 1773 | "version": "7.1.0", 1774 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", 1775 | "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", 1776 | "dev": true, 1777 | "requires": { 1778 | "has-flag": "^4.0.0" 1779 | }, 1780 | "dependencies": { 1781 | "has-flag": { 1782 | "version": "4.0.0", 1783 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 1784 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 1785 | "dev": true 1786 | } 1787 | } 1788 | }, 1789 | "table": { 1790 | "version": "5.4.6", 1791 | "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", 1792 | "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", 1793 | "dev": true, 1794 | "requires": { 1795 | "ajv": "^6.10.2", 1796 | "lodash": "^4.17.14", 1797 | "slice-ansi": "^2.1.0", 1798 | "string-width": "^3.0.0" 1799 | }, 1800 | "dependencies": { 1801 | "emoji-regex": { 1802 | "version": "7.0.3", 1803 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", 1804 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", 1805 | "dev": true 1806 | }, 1807 | "is-fullwidth-code-point": { 1808 | "version": "2.0.0", 1809 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 1810 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 1811 | "dev": true 1812 | }, 1813 | "string-width": { 1814 | "version": "3.1.0", 1815 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 1816 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 1817 | "dev": true, 1818 | "requires": { 1819 | "emoji-regex": "^7.0.1", 1820 | "is-fullwidth-code-point": "^2.0.0", 1821 | "strip-ansi": "^5.1.0" 1822 | } 1823 | } 1824 | } 1825 | }, 1826 | "text-table": { 1827 | "version": "0.2.0", 1828 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 1829 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", 1830 | "dev": true 1831 | }, 1832 | "through": { 1833 | "version": "2.3.8", 1834 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 1835 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 1836 | "dev": true 1837 | }, 1838 | "tmp": { 1839 | "version": "0.0.33", 1840 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", 1841 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", 1842 | "dev": true, 1843 | "requires": { 1844 | "os-tmpdir": "~1.0.2" 1845 | } 1846 | }, 1847 | "to-fast-properties": { 1848 | "version": "2.0.0", 1849 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 1850 | "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", 1851 | "dev": true 1852 | }, 1853 | "to-regex-range": { 1854 | "version": "5.0.1", 1855 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1856 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1857 | "dev": true, 1858 | "requires": { 1859 | "is-number": "^7.0.0" 1860 | } 1861 | }, 1862 | "tslib": { 1863 | "version": "1.10.0", 1864 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", 1865 | "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", 1866 | "dev": true 1867 | }, 1868 | "type-check": { 1869 | "version": "0.3.2", 1870 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", 1871 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", 1872 | "dev": true, 1873 | "requires": { 1874 | "prelude-ls": "~1.1.2" 1875 | } 1876 | }, 1877 | "type-fest": { 1878 | "version": "0.8.1", 1879 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", 1880 | "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", 1881 | "dev": true 1882 | }, 1883 | "typescript": { 1884 | "version": "3.6.5", 1885 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.5.tgz", 1886 | "integrity": "sha512-BEjlc0Z06ORZKbtcxGrIvvwYs5hAnuo6TKdNFL55frVDlB+na3z5bsLhFaIxmT+dPWgBIjMo6aNnTOgHHmHgiQ==", 1887 | "dev": true 1888 | }, 1889 | "uglify-js": { 1890 | "version": "3.7.7", 1891 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.7.tgz", 1892 | "integrity": "sha512-FeSU+hi7ULYy6mn8PKio/tXsdSXN35lm4KgV2asx00kzrLU9Pi3oAslcJT70Jdj7PHX29gGUPOT6+lXGBbemhA==", 1893 | "dev": true, 1894 | "optional": true, 1895 | "requires": { 1896 | "commander": "~2.20.3", 1897 | "source-map": "~0.6.1" 1898 | } 1899 | }, 1900 | "uri-js": { 1901 | "version": "4.2.2", 1902 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 1903 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 1904 | "dev": true, 1905 | "requires": { 1906 | "punycode": "^2.1.0" 1907 | } 1908 | }, 1909 | "useragent": { 1910 | "version": "2.3.0", 1911 | "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", 1912 | "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", 1913 | "dev": true, 1914 | "requires": { 1915 | "lru-cache": "4.1.x", 1916 | "tmp": "0.0.x" 1917 | } 1918 | }, 1919 | "v8-compile-cache": { 1920 | "version": "2.1.0", 1921 | "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", 1922 | "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", 1923 | "dev": true 1924 | }, 1925 | "which": { 1926 | "version": "1.3.1", 1927 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 1928 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 1929 | "dev": true, 1930 | "requires": { 1931 | "isexe": "^2.0.0" 1932 | } 1933 | }, 1934 | "will-call": { 1935 | "version": "1.0.1", 1936 | "resolved": "https://registry.npmjs.org/will-call/-/will-call-1.0.1.tgz", 1937 | "integrity": "sha512-1hEeV8SfBYhNRc/bNXeQfyUBX8Dl9SCYME3qXh99iZP9wJcnhnlBsoBw8Y0lXVZ3YuPsoxImTzBiol1ouNR/hg==", 1938 | "dev": true 1939 | }, 1940 | "word-wrap": { 1941 | "version": "1.2.3", 1942 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", 1943 | "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", 1944 | "dev": true 1945 | }, 1946 | "wordwrap": { 1947 | "version": "0.0.3", 1948 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", 1949 | "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", 1950 | "dev": true 1951 | }, 1952 | "wrappy": { 1953 | "version": "1.0.2", 1954 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1955 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1956 | "dev": true 1957 | }, 1958 | "write": { 1959 | "version": "1.0.3", 1960 | "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", 1961 | "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", 1962 | "dev": true, 1963 | "requires": { 1964 | "mkdirp": "^0.5.1" 1965 | } 1966 | }, 1967 | "yallist": { 1968 | "version": "2.1.2", 1969 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", 1970 | "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", 1971 | "dev": true 1972 | } 1973 | } 1974 | } 1975 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blankie", 3 | "version": "5.0.0", 4 | "description": "a content security policy plugin for hapi", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "lab -a @hapi/code -t 100 -L" 8 | }, 9 | "engines": { 10 | "node": ">= 8.9.0" 11 | }, 12 | "files": [ 13 | "lib" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "git://github.com/nlf/blankie.git" 18 | }, 19 | "keywords": [ 20 | "hapi", 21 | "csp", 22 | "plugin" 23 | ], 24 | "author": "Nathan LaFreniere ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/nlf/blankie/issues" 28 | }, 29 | "homepage": "https://github.com/nlf/blankie", 30 | "devDependencies": { 31 | "@hapi/code": "^8.0.1", 32 | "@hapi/hapi": "^19.1.1", 33 | "@hapi/lab": "^22.0.3", 34 | "@hapi/scooter": "^6.0.0", 35 | "@hapi/vision": "^6.0.0", 36 | "browser-agents": "^1.0.1", 37 | "handlebars": "^4.7.3" 38 | }, 39 | "dependencies": { 40 | "@hapi/hoek": "^9.0.3", 41 | "@hapi/joi": "^17.1.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/callbacks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Blankie = require('../'); 4 | const Hapi = require('@hapi/hapi'); 5 | const Scooter = require('@hapi/scooter'); 6 | 7 | const { expect } = require('@hapi/code'); 8 | 9 | const { describe, it } = exports.lab = require('@hapi/lab').script(); 10 | 11 | describe('Callbacks', () => { 12 | 13 | it('allows a callback as the only option', async () => { 14 | 15 | const cspCallback = function (req) { 16 | 17 | const options = {}; 18 | options.baseUri = 'self'; 19 | return options; 20 | }; 21 | 22 | const server = Hapi.server(); 23 | server.route({ 24 | method: 'GET', 25 | path: '/', 26 | handler: () => { 27 | 28 | return 'callback'; 29 | } 30 | }); 31 | 32 | await server.register([Scooter, { 33 | plugin: Blankie, 34 | options: cspCallback 35 | }]); 36 | 37 | const res = await server.inject({ 38 | method: 'GET', 39 | url: '/' 40 | }); 41 | 42 | expect(res.headers).to.contain('content-security-policy'); 43 | expect(res.headers['content-security-policy']).to.contain('base-uri \'self'); 44 | }); 45 | 46 | it('errors appropriately and skips setting header if callback returns invalid options', async () => { 47 | 48 | const cspCallback = function (req) { 49 | 50 | const options = {}; 51 | options.bananas = 'self'; 52 | return options; 53 | }; 54 | 55 | const server = Hapi.server(); 56 | server.route({ 57 | method: 'GET', 58 | path: '/', 59 | handler: () => { 60 | 61 | return 'callback'; 62 | } 63 | }); 64 | 65 | await server.register([Scooter, { 66 | plugin: Blankie, 67 | options: cspCallback 68 | }]); 69 | 70 | const res = await server.inject({ 71 | method: 'GET', 72 | url: '/' 73 | }); 74 | 75 | expect(res.headers).to.not.contain('content-security-policy'); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/chrome.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Agents = require('browser-agents'); 4 | const Blankie = require('../'); 5 | const Hapi = require('@hapi/hapi'); 6 | const Scooter = require('@hapi/scooter'); 7 | 8 | const { expect } = require('@hapi/code'); 9 | 10 | const { describe, it } = exports.lab = require('@hapi/lab').script(); 11 | 12 | const defaultRoute = { 13 | method: 'GET', 14 | path: '/', 15 | handler: () => { 16 | 17 | return 'defaults'; 18 | } 19 | }; 20 | 21 | describe('Chrome', () => { 22 | 23 | it('sends defaults for chrome > 25', async () => { 24 | 25 | const server = Hapi.server(); 26 | server.route(defaultRoute); 27 | await server.register([Scooter, Blankie]); 28 | const res = await server.inject({ 29 | method: 'GET', 30 | url: '/', 31 | headers: { 32 | 'User-Agent': Agents.Chrome['26.0'] 33 | } 34 | }); 35 | 36 | expect(res.statusCode).to.equal(200); 37 | expect(res.headers).to.contain('content-security-policy'); 38 | expect(res.headers['content-security-policy']).to.contain('default-src \'none\''); 39 | expect(res.headers['content-security-policy']).to.contain('script-src \'self\''); 40 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\''); 41 | expect(res.headers['content-security-policy']).to.contain('img-src \'self\''); 42 | expect(res.headers['content-security-policy']).to.contain('connect-src \'self\''); 43 | }); 44 | 45 | it('sends defaults for chrome < 14', async () => { 46 | 47 | const server = Hapi.server(); 48 | server.route(defaultRoute); 49 | await server.register([Scooter, Blankie]); 50 | const res = await server.inject({ 51 | method: 'GET', 52 | url: '/', 53 | headers: { 54 | 'User-Agent': Agents.Chrome['13.0'] 55 | } 56 | }); 57 | 58 | expect(res.statusCode).to.equal(200); 59 | expect(res.headers).to.contain('content-security-policy'); 60 | expect(res.headers['content-security-policy']).to.contain('default-src \'none\''); 61 | expect(res.headers['content-security-policy']).to.contain('script-src \'self\''); 62 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\''); 63 | expect(res.headers['content-security-policy']).to.contain('img-src \'self\''); 64 | expect(res.headers['content-security-policy']).to.contain('connect-src \'self\''); 65 | }); 66 | 67 | it('sends x-webkit-csp for > 14 and < 25', async () => { 68 | 69 | const server = Hapi.server(); 70 | server.route(defaultRoute); 71 | await server.register([Scooter, Blankie]); 72 | const res = await server.inject({ 73 | method: 'GET', 74 | url: '/', 75 | headers: { 76 | 'User-Agent': Agents.Chrome['15.0'] 77 | } 78 | }); 79 | 80 | expect(res.statusCode).to.equal(200); 81 | expect(res.headers).to.contain('x-webkit-csp'); 82 | expect(res.headers['x-webkit-csp']).to.contain('default-src \'none\''); 83 | expect(res.headers['x-webkit-csp']).to.contain('script-src \'self\''); 84 | expect(res.headers['x-webkit-csp']).to.contain('style-src \'self\''); 85 | expect(res.headers['x-webkit-csp']).to.contain('img-src \'self\''); 86 | expect(res.headers['x-webkit-csp']).to.contain('connect-src \'self\''); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /test/firefox.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Agents = require('browser-agents'); 4 | const Blankie = require('../'); 5 | const Hapi = require('@hapi/hapi'); 6 | const Scooter = require('@hapi/scooter'); 7 | 8 | const { expect } = require('@hapi/code'); 9 | 10 | const { describe, it } = exports.lab = require('@hapi/lab').script(); 11 | 12 | const defaultRoute = { 13 | method: 'GET', 14 | path: '/', 15 | handler: () => { 16 | 17 | return 'defaults'; 18 | } 19 | }; 20 | 21 | describe('Firefox', () => { 22 | 23 | it('sends defaults for firefox > 23', async () => { 24 | 25 | const server = Hapi.server(); 26 | server.route(defaultRoute); 27 | await server.register([Scooter, Blankie]); 28 | const res = await server.inject({ 29 | method: 'GET', 30 | url: '/', 31 | headers: { 32 | 'User-Agent': Agents.Firefox['24.0'] 33 | } 34 | }); 35 | 36 | expect(res.statusCode).to.equal(200); 37 | expect(res.headers).to.contain('content-security-policy'); 38 | expect(res.headers['content-security-policy']).to.contain('default-src \'none\''); 39 | expect(res.headers['content-security-policy']).to.contain('script-src \'self\''); 40 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\''); 41 | expect(res.headers['content-security-policy']).to.contain('img-src \'self\''); 42 | expect(res.headers['content-security-policy']).to.contain('connect-src \'self\''); 43 | }); 44 | 45 | it('sends firefox specific headers for >= 5 < 24', async () => { 46 | 47 | const server = Hapi.server(); 48 | server.route(defaultRoute); 49 | await server.register([Scooter, Blankie]); 50 | const res = await server.inject({ 51 | method: 'GET', 52 | url: '/', 53 | headers: { 54 | 'User-Agent': Agents.Firefox['23.0'] 55 | } 56 | }); 57 | 58 | expect(res.statusCode).to.equal(200); 59 | expect(res.headers).to.contain('x-content-security-policy'); 60 | expect(res.headers['x-content-security-policy']).to.contain('default-src \'none\''); 61 | expect(res.headers['x-content-security-policy']).to.contain('script-src \'self\''); 62 | expect(res.headers['x-content-security-policy']).to.contain('style-src \'self\''); 63 | expect(res.headers['x-content-security-policy']).to.contain('img-src \'self\''); 64 | expect(res.headers['x-content-security-policy']).to.contain('xhr-src \'self\''); 65 | }); 66 | 67 | it('sends allow instead of default-src for firefox 4', async () => { 68 | 69 | const server = Hapi.server(); 70 | server.route(defaultRoute); 71 | await server.register([Scooter, Blankie]); 72 | const res = await server.inject({ 73 | method: 'GET', 74 | url: '/', 75 | headers: { 76 | 'User-Agent': Agents.Firefox['4.0'] 77 | } 78 | }); 79 | 80 | expect(res.statusCode).to.equal(200); 81 | expect(res.headers).to.contain('x-content-security-policy'); 82 | expect(res.headers['x-content-security-policy']).to.contain('allow \'none\''); 83 | expect(res.headers['x-content-security-policy']).to.contain('script-src \'self\''); 84 | expect(res.headers['x-content-security-policy']).to.contain('style-src \'self\''); 85 | expect(res.headers['x-content-security-policy']).to.contain('img-src \'self\''); 86 | expect(res.headers['x-content-security-policy']).to.contain('xhr-src \'self\''); 87 | }); 88 | 89 | it('sends defaults for firefox < 4', async () => { 90 | 91 | const server = Hapi.server(); 92 | server.route(defaultRoute); 93 | await server.register([Scooter, Blankie]); 94 | const res = await server.inject({ 95 | method: 'GET', 96 | url: '/', 97 | headers: { 98 | 'User-Agent': Agents.Firefox['3.0'] 99 | } 100 | }); 101 | 102 | expect(res.statusCode).to.equal(200); 103 | expect(res.headers).to.contain('content-security-policy'); 104 | expect(res.headers['content-security-policy']).to.contain('default-src \'none\''); 105 | expect(res.headers['content-security-policy']).to.contain('script-src \'self\''); 106 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\''); 107 | expect(res.headers['content-security-policy']).to.contain('img-src \'self\''); 108 | expect(res.headers['content-security-policy']).to.contain('connect-src \'self\''); 109 | }); 110 | 111 | it('replaces unsafe-inline with inline-script for older firefox', async () => { 112 | 113 | const server = Hapi.server(); 114 | server.route(defaultRoute); 115 | await server.register([Scooter, { 116 | plugin: Blankie, 117 | options: { 118 | scriptSrc: 'unsafe-inline' 119 | } 120 | }]); 121 | 122 | const res = await server.inject({ 123 | method: 'GET', 124 | url: '/', 125 | headers: { 126 | 'User-Agent': Agents.Firefox['22.0'] 127 | } 128 | }); 129 | 130 | expect(res.statusCode).to.equal(200); 131 | expect(res.headers).to.contain('x-content-security-policy'); 132 | expect(res.headers['x-content-security-policy']).to.contain('script-src \'inline-script\''); 133 | }); 134 | 135 | it('replaces unsafe-eval with eval-script for older firefox', async () => { 136 | 137 | const server = Hapi.server(); 138 | server.route(defaultRoute); 139 | await server.register([Scooter, { 140 | plugin: Blankie, 141 | options: { 142 | scriptSrc: 'unsafe-eval' 143 | } 144 | }]); 145 | 146 | const res = await server.inject({ 147 | method: 'GET', 148 | url: '/', 149 | headers: { 150 | 'User-Agent': Agents.Firefox['22.0'] 151 | } 152 | }); 153 | 154 | expect(res.statusCode).to.equal(200); 155 | expect(res.headers).to.contain('x-content-security-policy'); 156 | expect(res.headers['x-content-security-policy']).to.contain('script-src \'eval-script\''); 157 | }); 158 | 159 | it('removes unsafe-eval from non script-src directives', async () => { 160 | 161 | const server = Hapi.server(); 162 | server.route(defaultRoute); 163 | await server.register([Scooter, { 164 | plugin: Blankie, 165 | options: { 166 | objectSrc: ['unsafe-inline', 'unsafe-eval', 'self'] 167 | } 168 | }]); 169 | 170 | const res = await server.inject({ 171 | method: 'GET', 172 | url: '/', 173 | headers: { 174 | 'User-Agent': Agents.Firefox['22.0'] 175 | } 176 | }); 177 | 178 | expect(res.statusCode).to.equal(200); 179 | expect(res.headers).to.contain('x-content-security-policy'); 180 | expect(res.headers['x-content-security-policy']).to.contain('object-src \'self\''); 181 | }); 182 | 183 | it('doesnt set empty strings if invalid values are all removed', async () => { 184 | 185 | const server = Hapi.server(); 186 | server.route(defaultRoute); 187 | await server.register([Scooter, { 188 | plugin: Blankie, 189 | options: { 190 | objectSrc: ['unsafe-inline', 'unsafe-eval'] 191 | } 192 | }]); 193 | 194 | const res = await server.inject({ 195 | method: 'GET', 196 | url: '/', 197 | headers: { 198 | 'User-Agent': Agents.Firefox['22.0'] 199 | } 200 | }); 201 | 202 | expect(res.statusCode).to.equal(200); 203 | expect(res.headers).to.contain('x-content-security-policy'); 204 | expect(res.headers['x-content-security-policy']).to.not.contain('object-src'); 205 | }); 206 | }); 207 | -------------------------------------------------------------------------------- /test/generic.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Blankie = require('../'); 4 | const Crypto = require('crypto'); 5 | const Hapi = require('@hapi/hapi'); 6 | const Scooter = require('@hapi/scooter'); 7 | const Vision = require('@hapi/vision'); 8 | 9 | const { expect } = require('@hapi/code'); 10 | 11 | const { describe, it } = exports.lab = require('@hapi/lab').script(); 12 | 13 | const defaultRoute = { 14 | method: 'GET', 15 | path: '/', 16 | handler: () => { 17 | 18 | return 'defaults'; 19 | } 20 | }; 21 | 22 | describe('Generic headers', () => { 23 | 24 | it('sends default headers', async () => { 25 | 26 | const server = Hapi.server(); 27 | server.route(defaultRoute); 28 | await server.register([Scooter, Blankie]); 29 | const res = await server.inject({ 30 | method: 'GET', 31 | url: '/' 32 | }); 33 | 34 | expect(res.statusCode).to.equal(200); 35 | expect(res.headers).to.contain('content-security-policy'); 36 | expect(res.headers['content-security-policy']).to.contain('base-uri \'self'); 37 | expect(res.headers['content-security-policy']).to.contain('default-src \'none\''); 38 | expect(res.headers['content-security-policy']).to.contain('script-src \'self\' \'nonce-'); // only checks for the nonce- prefix since it's a random value 39 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\''); 40 | expect(res.headers['content-security-policy']).to.contain('img-src \'self\''); 41 | expect(res.headers['content-security-policy']).to.contain('connect-src \'self\''); 42 | expect(res.headers['content-security-policy']).to.contain('worker-src \'self\''); 43 | }); 44 | 45 | it('allows setting base-uri', async () => { 46 | 47 | const server = Hapi.server(); 48 | const options = { 49 | baseUri: ['unsafe-inline', 'https://hapijs.com', 'blob:'] 50 | }; 51 | server.route(defaultRoute); 52 | await server.register([Scooter, { plugin: Blankie, options }]); 53 | const res = await server.inject({ 54 | method: 'GET', 55 | url: '/' 56 | }); 57 | 58 | expect(res.statusCode).to.equal(200); 59 | expect(res.headers).to.contain('content-security-policy'); 60 | expect(res.headers['content-security-policy']).to.contain('base-uri \'unsafe-inline\' https://hapijs.com blob:'); 61 | expect(res.headers['content-security-policy']).to.contain('default-src \'none\''); 62 | expect(res.headers['content-security-policy']).to.contain('script-src \'self\' \'nonce-'); // only checks for the nonce- prefix since it's a random value 63 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\''); 64 | expect(res.headers['content-security-policy']).to.contain('img-src \'self\''); 65 | expect(res.headers['content-security-policy']).to.contain('connect-src \'self\''); 66 | expect(res.headers['content-security-policy']).to.contain('worker-src \'self\''); 67 | }); 68 | 69 | it('adds a nonce to view contexts', async () => { 70 | 71 | const server = Hapi.server(); 72 | await server.register([Scooter, Blankie, Vision]); 73 | 74 | server.views({ 75 | engines: { 76 | html: require('handlebars') 77 | }, 78 | path: __dirname + '/templates' 79 | }); 80 | 81 | server.route({ 82 | method: 'GET', 83 | path: '/', 84 | handler: (request, h) => { 85 | 86 | return h.view('nonce'); 87 | } 88 | }); 89 | 90 | const res = await server.inject({ 91 | method: 'GET', 92 | url: '/' 93 | }); 94 | 95 | expect(res.statusCode).to.equal(200); 96 | expect(res.result.length).to.be.above(1); 97 | const nonces = res.result.trim().split('\n'); 98 | expect(res.headers).to.contain('content-security-policy'); 99 | expect(res.headers['content-security-policy']).to.contain('default-src \'none\''); 100 | expect(res.headers['content-security-policy']).to.contain('script-src \'self\' \'nonce-' + nonces[0] + '\''); 101 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\' \'nonce-' + nonces[1] + '\''); 102 | expect(res.headers['content-security-policy']).to.contain('img-src \'self\''); 103 | expect(res.headers['content-security-policy']).to.contain('connect-src \'self\''); 104 | expect(res.headers['content-security-policy']).to.contain('worker-src \'self\''); 105 | }); 106 | 107 | it('allows setting unsafe-inline in combination with nonce on script-src', async () => { 108 | 109 | const server = Hapi.server(); 110 | const options = { 111 | scriptSrc: ['unsafe-inline'] 112 | }; 113 | server.route(defaultRoute); 114 | await server.register([Scooter, { plugin: Blankie, options }]); 115 | const res = await server.inject({ 116 | method: 'GET', 117 | url: '/' 118 | }); 119 | 120 | expect(res.statusCode).to.equal(200); 121 | expect(res.headers).to.contain('content-security-policy'); 122 | expect(res.headers['content-security-policy']).to.contain('default-src \'none\''); 123 | expect(res.headers['content-security-policy']).to.contain('script-src \'unsafe-inline\' \'nonce-'); // only checks for the nonce- prefix since it's a random value 124 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\''); 125 | expect(res.headers['content-security-policy']).to.contain('img-src \'self\''); 126 | expect(res.headers['content-security-policy']).to.contain('connect-src \'self\''); 127 | expect(res.headers['content-security-policy']).to.contain('worker-src \'self\''); 128 | }); 129 | 130 | it('allows settings strict-dynamic with corresponding nonces', async () => { 131 | 132 | const server = Hapi.server(); 133 | const options = { 134 | scriptSrc: ['strict-dynamic'], 135 | styleSrc: ['strict-dynamic'], 136 | generateNonces: true 137 | }; 138 | server.route(defaultRoute); 139 | await server.register([Scooter, { plugin: Blankie, options }]); 140 | const res = await server.inject({ 141 | method: 'GET', 142 | url: '/' 143 | }); 144 | 145 | expect(res.statusCode).to.equal(200); 146 | expect(res.headers).to.contain('content-security-policy'); 147 | expect(res.headers['content-security-policy']).to.contain('script-src \'strict-dynamic\' \'nonce-'); 148 | expect(res.headers['content-security-policy']).to.contain('style-src \'strict-dynamic\' \'nonce-'); 149 | }); 150 | 151 | it('allows creating nonces for only script-src', async () => { 152 | 153 | const server = Hapi.server(); 154 | const options = { 155 | generateNonces: 'script' 156 | }; 157 | server.route(defaultRoute); 158 | await server.register([Scooter, { plugin: Blankie, options }]); 159 | const res = await server.inject({ 160 | method: 'GET', 161 | url: '/' 162 | }); 163 | 164 | expect(res.statusCode).to.equal(200); 165 | expect(res.headers).to.contain('content-security-policy'); 166 | expect(res.headers['content-security-policy']).to.contain('script-src \'self\' \'nonce-'); 167 | expect(res.headers['content-security-policy']).to.not.contain('style-src \'self\' \'nonce-'); 168 | }); 169 | 170 | it('allows creating nonces for only style-src', async () => { 171 | 172 | const server = Hapi.server(); 173 | const options = { 174 | generateNonces: 'style' 175 | }; 176 | server.route(defaultRoute); 177 | await server.register([Scooter, { plugin: Blankie, options }]); 178 | const res = await server.inject({ 179 | method: 'GET', 180 | url: '/' 181 | }); 182 | 183 | expect(res.statusCode).to.equal(200); 184 | expect(res.headers).to.contain('content-security-policy'); 185 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\' \'nonce-'); 186 | expect(res.headers['content-security-policy']).to.not.contain('script-src \'self\' \'nonce-'); 187 | }); 188 | 189 | it('sets headers when content-type is set and is text/html', async () => { 190 | 191 | const server = Hapi.server(); 192 | server.route({ 193 | method: 'GET', 194 | path: '/', 195 | handler: (request, h) => { 196 | 197 | return h.response('test').type('text/html'); 198 | } 199 | }); 200 | 201 | await server.register([Scooter, Blankie]); 202 | const res = await server.inject({ 203 | method: 'GET', 204 | url: '/' 205 | }); 206 | 207 | expect(res.statusCode).to.equal(200); 208 | expect(res.headers).to.contain('content-security-policy'); 209 | }); 210 | 211 | it('does not set headers when content-type is set and is not text/html', async () => { 212 | 213 | const server = Hapi.server(); 214 | server.route({ 215 | method: 'GET', 216 | path: '/', 217 | handler: (request, h) => { 218 | 219 | return h.response({ some: 'body' }).type('application/json'); 220 | } 221 | }); 222 | 223 | await server.register([Scooter, Blankie]); 224 | const res = await server.inject({ 225 | method: 'GET', 226 | url: '/' 227 | }); 228 | 229 | expect(res.statusCode).to.equal(200); 230 | expect(res.headers).to.not.contain('content-security-policy'); 231 | }); 232 | 233 | it('does not blow up if Crypto.pseudoRandomBytes happens to throw', async () => { 234 | 235 | const server = Hapi.server(); 236 | server.route(defaultRoute); 237 | await server.register([Scooter, Blankie]); 238 | 239 | Crypto._randomBytes = Crypto.randomBytes; 240 | Crypto.randomBytes = function () { 241 | 242 | throw new Error('mocked failure'); 243 | }; 244 | 245 | const res = await server.inject({ 246 | method: 'GET', 247 | url: '/' 248 | }); 249 | 250 | expect(res.statusCode).to.equal(200); 251 | expect(res.headers).to.contain('content-security-policy'); 252 | expect(res.headers['content-security-policy']).to.contain('default-src \'none\''); 253 | expect(res.headers['content-security-policy']).to.contain('script-src \'self\' \'nonce-'); // only checks for the nonce- prefix since it's a random value 254 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\''); 255 | expect(res.headers['content-security-policy']).to.contain('img-src \'self\''); 256 | expect(res.headers['content-security-policy']).to.contain('connect-src \'self\''); 257 | expect(res.headers['content-security-policy']).to.contain('worker-src \'self\''); 258 | Crypto.randomBytes = Crypto._randomBytes; 259 | delete Crypto._randomBytes; 260 | }); 261 | 262 | it('sends default headers when scooter is not loaded', async () => { 263 | 264 | const server = Hapi.server(); 265 | server.route(defaultRoute); 266 | await server.register(Blankie); 267 | const res = await server.inject({ 268 | method: 'GET', 269 | url: '/' 270 | }); 271 | 272 | expect(res.statusCode).to.equal(200); 273 | expect(res.headers).to.contain('content-security-policy'); 274 | expect(res.headers['content-security-policy']).to.contain('default-src \'none\''); 275 | expect(res.headers['content-security-policy']).to.contain('script-src \'self\''); 276 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\''); 277 | expect(res.headers['content-security-policy']).to.contain('img-src \'self\''); 278 | expect(res.headers['content-security-policy']).to.contain('connect-src \'self\''); 279 | expect(res.headers['content-security-policy']).to.contain('worker-src \'self\''); 280 | }); 281 | 282 | it('sends report only headers when requested', async () => { 283 | 284 | const server = Hapi.server(); 285 | server.route(defaultRoute); 286 | await server.register([Scooter, { 287 | plugin: Blankie, 288 | options: { 289 | defaultSrc: 'self', 290 | reportOnly: true, 291 | reportUri: '/csp_report' 292 | } 293 | }]); 294 | 295 | const res = await server.inject({ 296 | method: 'GET', 297 | url: '/' 298 | }); 299 | 300 | expect(res.statusCode).to.equal(200); 301 | expect(res.headers).to.contain('content-security-policy-report-only'); 302 | expect(res.headers['content-security-policy-report-only']).to.contain('default-src \'self\''); 303 | expect(res.headers['content-security-policy-report-only']).to.contain('report-uri /csp_report'); 304 | }); 305 | 306 | it('does not crash when responding with an error', async () => { 307 | 308 | const server = Hapi.server(); 309 | server.route({ 310 | method: 'GET', 311 | path: '/', 312 | handler: (request, h) => { 313 | 314 | throw new Error('broken!'); 315 | } 316 | }); 317 | await server.register([Scooter, Blankie]); 318 | const res = await server.inject({ 319 | method: 'GET', 320 | url: '/' 321 | }); 322 | 323 | expect(res.statusCode).to.equal(500); 324 | expect(res.headers).to.contain('content-security-policy'); 325 | expect(res.headers['content-security-policy']).to.contain('default-src \'none\''); 326 | expect(res.headers['content-security-policy']).to.contain('script-src \'self\''); 327 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\''); 328 | expect(res.headers['content-security-policy']).to.contain('img-src \'self\''); 329 | expect(res.headers['content-security-policy']).to.contain('connect-src \'self\''); 330 | expect(res.headers['content-security-policy']).to.contain('worker-src \'self\''); 331 | }); 332 | 333 | it('allows setting the sandbox directive with no values', async () => { 334 | 335 | const server = Hapi.server(); 336 | server.route(defaultRoute); 337 | await server.register([Scooter, { 338 | plugin: Blankie, 339 | options: { 340 | sandbox: true 341 | } 342 | }]); 343 | 344 | const res = await server.inject({ 345 | method: 'GET', 346 | url: '/' 347 | }); 348 | 349 | expect(res.statusCode).to.equal(200); 350 | expect(res.headers).to.contain('content-security-policy'); 351 | expect(res.headers['content-security-policy']).to.contain('sandbox'); 352 | }); 353 | 354 | it('allows setting array directives to a single string', async () => { 355 | 356 | const server = Hapi.server(); 357 | server.route(defaultRoute); 358 | await server.register([Scooter, { 359 | plugin: Blankie, 360 | options: { 361 | defaultSrc: '*' 362 | } 363 | }]); 364 | 365 | const res = await server.inject({ 366 | method: 'GET', 367 | url: '/' 368 | }); 369 | 370 | expect(res.statusCode).to.equal(200); 371 | expect(res.headers).to.contain('content-security-policy'); 372 | expect(res.headers['content-security-policy']).to.contain('default-src *'); 373 | }); 374 | 375 | it('allows setting array directives to an array of strings', async () => { 376 | 377 | const server = Hapi.server(); 378 | server.route(defaultRoute); 379 | await server.register([Scooter, { 380 | plugin: Blankie, 381 | options: { 382 | defaultSrc: ['*', 'self'] 383 | } 384 | }]); 385 | 386 | const res = await server.inject({ 387 | method: 'GET', 388 | url: '/' 389 | }); 390 | 391 | expect(res.statusCode).to.equal(200); 392 | expect(res.headers).to.contain('content-security-policy'); 393 | expect(res.headers['content-security-policy']).to.contain('default-src * \'self\''); 394 | }); 395 | 396 | it('exposes nonces on request.plugins.blankie.nonces', async () => { 397 | 398 | const server = Hapi.server(); 399 | const options = { 400 | generateNonces: true 401 | }; 402 | server.route({ 403 | method: 'GET', 404 | path: '/', 405 | handler: (request) => { 406 | 407 | return request.plugins.blankie.nonces; 408 | } 409 | }); 410 | await server.register([Scooter, { plugin: Blankie, options }]); 411 | const res = await server.inject({ 412 | method: 'GET', 413 | url: '/' 414 | }); 415 | 416 | expect(res.statusCode).to.equal(200); 417 | expect(res.result).to.contain(['style', 'script']); 418 | expect(res.result.style).to.be.a.string(); 419 | expect(res.result.script).to.be.a.string(); 420 | }); 421 | 422 | it('skips headers on OPTIONS requests', async () => { 423 | 424 | const server = Hapi.server(); 425 | const options = { 426 | generateNonces: true 427 | }; 428 | server.route({ 429 | method: 'OPTIONS', 430 | path: '/', 431 | handler: () => { 432 | 433 | return ''; 434 | } 435 | }); 436 | await server.register([Scooter, { plugin: Blankie, options }]); 437 | const res = await server.inject({ 438 | method: 'OPTIONS', 439 | url: '/' 440 | }); 441 | 442 | expect(res.statusCode).to.equal(204); 443 | expect(res.headers).to.not.include('content-security-policy'); 444 | }); 445 | 446 | it('does not throw on 404 when generating nonces', async () => { 447 | 448 | const server = Hapi.server(); 449 | const options = { 450 | generateNonces: true 451 | }; 452 | server.route({ 453 | method: 'GET', 454 | path: '/', 455 | handler: () => { 456 | 457 | return ''; 458 | } 459 | }); 460 | await server.register([Scooter, { plugin: Blankie, options }]); 461 | const res = await server.inject({ 462 | method: 'GET', 463 | url: '/404' 464 | }); 465 | 466 | expect(res.statusCode).to.equal(404); 467 | expect(res.headers).to.include('content-security-policy'); 468 | expect(res.headers['content-security-policy']).to.contain('script-src \'self\' \'nonce-'); 469 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\' \'nonce-'); 470 | }); 471 | 472 | it('can be disabled on a single route', async () => { 473 | 474 | const server = Hapi.server(); 475 | server.route(defaultRoute); 476 | server.route({ 477 | method: 'GET', 478 | path: '/disabled', 479 | config: { 480 | handler: () => { 481 | 482 | return 'disabled'; 483 | }, 484 | plugins: { 485 | blankie: false 486 | } 487 | } 488 | }); 489 | await server.register([Scooter, Blankie]); 490 | const res = await server.inject({ 491 | method: 'GET', 492 | url: '/' 493 | }); 494 | 495 | expect(res.statusCode).to.equal(200); 496 | expect(res.headers).to.contain('content-security-policy'); 497 | expect(res.headers['content-security-policy']).to.contain('default-src \'none\''); 498 | expect(res.headers['content-security-policy']).to.contain('script-src \'self\''); 499 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\''); 500 | expect(res.headers['content-security-policy']).to.contain('img-src \'self\''); 501 | expect(res.headers['content-security-policy']).to.contain('connect-src \'self\''); 502 | expect(res.headers['content-security-policy']).to.contain('worker-src \'self\''); 503 | 504 | const res2 = await server.inject({ 505 | method: 'GET', 506 | url: '/disabled' 507 | }); 508 | 509 | expect(res2.statusCode).to.equal(200); 510 | expect(res2.headers).to.not.contain('content-security-policy'); 511 | }); 512 | 513 | it('can be overridden on a single route', async () => { 514 | 515 | const server = Hapi.server(); 516 | server.route(defaultRoute); 517 | server.route({ 518 | method: 'GET', 519 | path: '/overridden', 520 | config: { 521 | handler: () => { 522 | 523 | return 'overridden'; 524 | }, 525 | plugins: { 526 | blankie: { 527 | defaultSrc: 'self' 528 | } 529 | } 530 | } 531 | }); 532 | await server.register([Scooter, Blankie]); 533 | const res = await server.inject({ 534 | method: 'GET', 535 | url: '/' 536 | }); 537 | 538 | expect(res.statusCode).to.equal(200); 539 | expect(res.headers).to.contain('content-security-policy'); 540 | expect(res.headers['content-security-policy']).to.contain('default-src \'none\''); 541 | expect(res.headers['content-security-policy']).to.contain('script-src \'self\''); 542 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\''); 543 | expect(res.headers['content-security-policy']).to.contain('img-src \'self\''); 544 | expect(res.headers['content-security-policy']).to.contain('connect-src \'self\''); 545 | expect(res.headers['content-security-policy']).to.contain('worker-src \'self\''); 546 | 547 | const res2 = await server.inject({ 548 | method: 'GET', 549 | url: '/overridden' 550 | }); 551 | 552 | expect(res2.statusCode).to.equal(200); 553 | expect(res2.headers).to.contain('content-security-policy'); 554 | expect(res2.headers['content-security-policy']).to.contain('default-src \'self\''); 555 | }); 556 | 557 | it('self disables when a route override is invalid', async () => { 558 | 559 | const server = Hapi.server(); 560 | server.route(defaultRoute); 561 | server.route({ 562 | method: 'GET', 563 | path: '/invalid', 564 | config: { 565 | handler: () => { 566 | 567 | return 'invalid'; 568 | }, 569 | plugins: { 570 | blankie: { 571 | sandbox: 'self' 572 | } 573 | } 574 | } 575 | }); 576 | await server.register([Scooter, Blankie]); 577 | const res = await server.inject({ 578 | method: 'GET', 579 | url: '/' 580 | }); 581 | 582 | expect(res.statusCode).to.equal(200); 583 | expect(res.headers).to.contain('content-security-policy'); 584 | expect(res.headers['content-security-policy']).to.contain('default-src \'none\''); 585 | expect(res.headers['content-security-policy']).to.contain('script-src \'self\''); 586 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\''); 587 | expect(res.headers['content-security-policy']).to.contain('img-src \'self\''); 588 | expect(res.headers['content-security-policy']).to.contain('connect-src \'self\''); 589 | expect(res.headers['content-security-policy']).to.contain('worker-src \'self\''); 590 | 591 | const res2 = await server.inject({ 592 | method: 'GET', 593 | url: '/invalid' 594 | }); 595 | 596 | expect(res2.statusCode).to.equal(200); 597 | expect(res2.headers).to.not.contain('content-security-policy'); 598 | }); 599 | }); 600 | -------------------------------------------------------------------------------- /test/ie.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Agents = require('browser-agents'); 4 | const Blankie = require('../'); 5 | const Hapi = require('@hapi/hapi'); 6 | const Scooter = require('@hapi/scooter'); 7 | 8 | const { expect } = require('@hapi/code'); 9 | 10 | const { describe, it } = exports.lab = require('@hapi/lab').script(); 11 | 12 | const defaultRoute = { 13 | method: 'GET', 14 | path: '/', 15 | handler: () => { 16 | 17 | return 'defaults'; 18 | } 19 | }; 20 | 21 | describe('Internet Explorer', () => { 22 | 23 | it('sends nothing by default', async () => { 24 | 25 | const server = Hapi.server(); 26 | server.route(defaultRoute); 27 | await server.register([Scooter, Blankie]); 28 | const res = await server.inject({ 29 | method: 'GET', 30 | url: '/', 31 | headers: { 32 | 'User-Agent': Agents.IE.random() 33 | } 34 | }); 35 | 36 | expect(res.statusCode).to.equal(200); 37 | expect(res.headers).to.contain('x-content-security-policy'); 38 | expect(res.headers['x-content-security-policy']).to.equal(''); 39 | }); 40 | 41 | it('sends sandbox headers if set', async () => { 42 | 43 | const server = Hapi.server(); 44 | server.route(defaultRoute); 45 | await server.register([Scooter, { 46 | plugin: Blankie, 47 | options: { 48 | sandbox: 'allow-same-origin' 49 | } 50 | }]); 51 | 52 | const res = await server.inject({ 53 | method: 'GET', 54 | url: '/', 55 | headers: { 56 | 'User-Agent': Agents.IE.random() 57 | } 58 | }); 59 | 60 | expect(res.statusCode).to.equal(200); 61 | expect(res.headers).to.contain('x-content-security-policy'); 62 | expect(res.headers['x-content-security-policy']).to.equal('sandbox allow-same-origin'); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Blankie = require('../'); 4 | const Hapi = require('@hapi/hapi'); 5 | const Scooter = require('@hapi/scooter'); 6 | 7 | const { expect } = require('@hapi/code'); 8 | 9 | const { describe, it } = exports.lab = require('@hapi/lab').script(); 10 | 11 | describe('Blankie', () => { 12 | 13 | it('loads as a plugin', async () => { 14 | 15 | const server = Hapi.server(); 16 | await server.register([Scooter, Blankie]); 17 | }); 18 | 19 | it('errors with invalid options', async () => { 20 | 21 | const server = Hapi.server(); 22 | await expect(server.register([Scooter, { 23 | plugin: Blankie, 24 | options: { 25 | reportOnly: 'invalid value' 26 | } 27 | }])).to.reject(Error, '"reportOnly" must be a boolean'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/safari.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Agents = require('browser-agents'); 4 | const Blankie = require('../'); 5 | const Hapi = require('@hapi/hapi'); 6 | const Scooter = require('@hapi/scooter'); 7 | 8 | const { expect } = require('@hapi/code'); 9 | 10 | const { describe, it } = exports.lab = require('@hapi/lab').script(); 11 | 12 | const defaultRoute = { 13 | method: 'GET', 14 | path: '/', 15 | handler: () => { 16 | 17 | return 'defaults'; 18 | } 19 | }; 20 | 21 | describe('Safari', () => { 22 | 23 | it('sends defaults for >= 7.0', async () => { 24 | 25 | const server = Hapi.server(); 26 | server.route(defaultRoute); 27 | await server.register([Scooter, Blankie]); 28 | const res = await server.inject({ 29 | method: 'GET', 30 | url: '/', 31 | headers: { 32 | 'User-Agent': Agents.Safari['7.0'] 33 | } 34 | }); 35 | 36 | expect(res.statusCode).to.equal(200); 37 | expect(res.headers).to.contain('content-security-policy'); 38 | expect(res.headers['content-security-policy']).to.contain('default-src \'none\''); 39 | expect(res.headers['content-security-policy']).to.contain('script-src \'self\''); 40 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\''); 41 | expect(res.headers['content-security-policy']).to.contain('img-src \'self\''); 42 | expect(res.headers['content-security-policy']).to.contain('connect-src \'self\''); 43 | }); 44 | 45 | it('sends x-webkit-csp for 6.0', async () => { 46 | 47 | const server = Hapi.server(); 48 | server.route(defaultRoute); 49 | await server.register([Scooter, Blankie]); 50 | const res = await server.inject({ 51 | method: 'GET', 52 | url: '/', 53 | headers: { 54 | 'User-Agent': Agents.Safari['6.0'] 55 | } 56 | }); 57 | 58 | expect(res.statusCode).to.equal(200); 59 | expect(res.headers).to.contain('x-webkit-csp'); 60 | expect(res.headers['x-webkit-csp']).to.contain('default-src \'none\''); 61 | expect(res.headers['x-webkit-csp']).to.contain('script-src \'self\''); 62 | expect(res.headers['x-webkit-csp']).to.contain('style-src \'self\''); 63 | expect(res.headers['x-webkit-csp']).to.contain('img-src \'self\''); 64 | expect(res.headers['x-webkit-csp']).to.contain('connect-src \'self\''); 65 | }); 66 | 67 | it('sends x-webkit-csp for 5.0 if oldSafari = true', async () => { 68 | 69 | const server = Hapi.server(); 70 | server.route(defaultRoute); 71 | await server.register([Scooter, { 72 | plugin: Blankie, 73 | options: { 74 | oldSafari: true 75 | } 76 | }]); 77 | 78 | const res = await server.inject({ 79 | method: 'GET', 80 | url: '/', 81 | headers: { 82 | 'User-Agent': Agents.Safari['5.0'] 83 | } 84 | }); 85 | 86 | expect(res.statusCode).to.equal(200); 87 | expect(res.headers).to.contain('x-webkit-csp'); 88 | expect(res.headers['x-webkit-csp']).to.contain('default-src \'none\''); 89 | expect(res.headers['x-webkit-csp']).to.contain('script-src \'self\''); 90 | expect(res.headers['x-webkit-csp']).to.contain('style-src \'self\''); 91 | expect(res.headers['x-webkit-csp']).to.contain('img-src \'self\''); 92 | expect(res.headers['x-webkit-csp']).to.contain('connect-src \'self\''); 93 | }); 94 | 95 | it('sends default header for 5.0 if oldSafari = false', async () => { 96 | 97 | const server = Hapi.server(); 98 | server.route(defaultRoute); 99 | await server.register([Scooter, Blankie]); 100 | const res = await server.inject({ 101 | method: 'GET', 102 | url: '/', 103 | headers: { 104 | 'User-Agent': Agents.Safari['5.0'] 105 | } 106 | }); 107 | 108 | expect(res.statusCode).to.equal(200); 109 | expect(res.headers).to.contain('content-security-policy'); 110 | expect(res.headers['content-security-policy']).to.contain('default-src \'none\''); 111 | expect(res.headers['content-security-policy']).to.contain('script-src \'self\''); 112 | expect(res.headers['content-security-policy']).to.contain('style-src \'self\''); 113 | expect(res.headers['content-security-policy']).to.contain('img-src \'self\''); 114 | expect(res.headers['content-security-policy']).to.contain('connect-src \'self\''); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /test/templates/nonce.html: -------------------------------------------------------------------------------- 1 | {{ script-nonce }} 2 | {{ style-nonce }} 3 | --------------------------------------------------------------------------------