├── .gitignore ├── test ├── mocha.opts └── tests.js ├── .travis.yml ├── .editorconfig ├── package.json ├── .eslintrc.json ├── index.js ├── README.md ├── policies.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --ui tdd 2 | --bail 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | # Tab indent, JS style 8 | [*] 9 | end_of_line = lf 10 | insert_final_newline = true 11 | indent_size = 2 12 | indent_style = tab 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csp-by-api", 3 | "version": "2.3.0", 4 | "description": "Easily build a Content Security Policy (CSP) by specifying APIs by name", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/mikemaccana/csp-by-api.git" 12 | }, 13 | "keywords": [ 14 | "content-security-policy", 15 | "csp" 16 | ], 17 | "author": "Mike MacCana ", 18 | "license": "MIT", 19 | "dependencies": { 20 | "agave": "^2.2.1", 21 | "lodash": "^3.10.1" 22 | }, 23 | "devDependencies": { 24 | "mocha": "^3.3.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | "extends": "eslint:recommended", 10 | "parserOptions": { 11 | "ecmaVersion": 2017 12 | }, 13 | "globals": { 14 | "mixpanel": true, 15 | "serverVars": true, 16 | "avkind": true, 17 | "kind": true, 18 | "Stripe": true, 19 | // Last two are from DigiCert site seal 20 | "__dcid": true, 21 | "__Cascade": true 22 | }, 23 | "plugins": [ 24 | "must-use-await" 25 | ], 26 | "rules": { 27 | "indent": [ 28 | "error", 29 | "tab" 30 | ], 31 | "must-use-await/must-use-await": 1, // 1 warn, 2 error 32 | "linebreak-style": "off", 33 | "quotes": "off", 34 | "no-console": "off", 35 | "semi": "off", 36 | "no-unused-vars": "off", 37 | "no-debugger": "off" 38 | } 39 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var agave = require('agave')('av'), 2 | lodash = require('lodash'), 3 | appPolicies = require(__dirname + '/policies.js'); 4 | 5 | var log = console.log.bind(console); 6 | 7 | var domainToWildcard = function(domain) { 8 | var parts = domain.split('.') 9 | var wildcard = null 10 | if (parts.length > 1) { 11 | wildcard = '*.' + parts.splice(1).join('.') 12 | } 13 | return wildcard 14 | } 15 | 16 | var removeDuplicates = function(policy) { 17 | // After the sort, '*.domain.com' are now first 18 | policy.avforEach(function(key) { 19 | if (avkind(policy[key]) === 'Array') { 20 | policy[key] = policy[key].sort(); 21 | } 22 | }) 23 | 24 | // We will now remove any FQDNs where the wildcard already exists 25 | var uniquePolicy = {} 26 | policy.avforEach(function(key) { 27 | var values = policy[key] 28 | if ( avkind(values) === 'Array' ) { 29 | if ( ! uniquePolicy[key] ) { 30 | uniquePolicy[key] = [] 31 | } 32 | values.forEach(function(value) { 33 | var wildCardParent = domainToWildcard(value) 34 | if (wildCardParent && uniquePolicy[key].includes(wildCardParent)) { 35 | // log('Skipping', value, 'as we already have', wildCardParent) 36 | return 37 | } 38 | uniquePolicy[key].push(value) 39 | }) 40 | } else { 41 | // Not an array, so just copy 42 | uniquePolicy[key] = policy[key] 43 | } 44 | }) 45 | return uniquePolicy 46 | } 47 | 48 | var makeContentSecurityPolicy = function(currentPolicy, appNames) { 49 | var combinedPolicy = currentPolicy || {}; 50 | appNames.forEach(function(policyNameOrAppPolicyObj) { 51 | var appPolicy; 52 | if (lodash.isString(policyNameOrAppPolicyObj)) { 53 | appPolicy = appPolicies[policyNameOrAppPolicyObj]; 54 | } else { 55 | appPolicy = policyNameOrAppPolicyObj; 56 | } 57 | if (!appPolicy) { 58 | // Throw a hard error and don't allow our app to start 59 | throw new Error('missing CSP policy ' + appName) 60 | } 61 | appPolicy.avforEach(function(key) { 62 | combinedPolicy[key] = lodash.union(combinedPolicy[key], appPolicy[key]) 63 | }) 64 | }) 65 | var uniquePolicy = removeDuplicates(combinedPolicy) 66 | return uniquePolicy 67 | } 68 | 69 | module.exports = makeContentSecurityPolicy; 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Prevent Cross Site Scripting by building a CSP policy specifying services you use 2 | 3 | `csp-by-api` makes CSP management easier by letting developers specify the services they use by name - these are then merged into the base policy to create the final CSP. 4 | 5 | 6 | 7 | ## Included policies 8 | 9 | This package itself knows the required CSP policies for: 10 | 11 | - `braintree` [Braintree Payments](https://developers.braintreepayments.com/reference/client-reference/javascript/v2/best-practices#using-braintree.js-with-a-content-security-policy) 12 | - `clearbit` [Clearbit](https://clearbit.com/) 13 | - `digiCertSiteSeal` [DigiCert Site Seal](https://www.digicert.com/ssl-support/digicert-security-seal.htm) 14 | - `googleAnalytics` [Google Analytics](https://www.google.co.uk/analytics/) 15 | - `googleFonts` [Google Fonts](https://www.google.com/fonts) 16 | - `gravatar` [Gravatar](https://en.gravatar.com/) 17 | - `magicSignup` [Magic Signup](https://magicsignup.com) 18 | - `mixpanel` [Mixpanel](https://mixpanel.com) 19 | - `olark` [Olark](https://olark.com) 20 | - `perfectAudience` [Perfect Audience](http://www.perfectaudience.com/) 21 | - `ractive` [Ractive.js](http://www.ractivejs.org/) 22 | - `rollbar` [Rollbar](https://rollbar.com) 23 | - `stormpath` [Stormpath](https://stormpath.com) 24 | - `stripe` [Stripe](https://stripe.com) 25 | - `twitter` [Twitter oembed API](https://dev.twitter.com/web/embedded-tweets) 26 | - `twitterAnalytics` [Twitter analytics](https://analytics.twitter.com) 27 | - `typekit` [Typekit](https://typekit.com) 28 | - `vimeo` [Vimeo](https://vimeo.com) 29 | 30 | Official policies are used wherever they're made available, and all are tested in a production app. 31 | 32 | ## Usage 33 | 34 | CSP By API doesn't implement CSP in node. Use an existing node CSP implementation like [Helmet](https://www.npmjs.com/package/helmet) or [express-csp](https://github.com/yahoo/express-csp) for that. Instead, CSP By API **significantly** cuts down on: 35 | 36 | - the amount of CSP research needed 37 | - the amount of CSP management 38 | 39 | For your app. For example: 40 | 41 | var cspByAPI = require('csp-by-api') 42 | 43 | // This is the policy for your own app only. You don't need to worry about third parties at all! 44 | var basePolicy = { 45 | defaultSrc: [CSP_SELF], 46 | scriptSrc: [CSP_SELF], 47 | styleSrc: [CSP_SELF, CSP_UNSAFE_INLINE], 48 | fontSrc: [], 49 | imgSrc: [CSP_SELF, 'data:'], 50 | connectSrc: [CSP_SELF], 51 | frameSrc: [], 52 | reportUri: "/csp-violation", 53 | reportOnly: true 54 | } 55 | 56 | Then add the apps you use. `csp-by-api` will combine them for you: 57 | 58 | var policy = cspByAPI(basePolicy, [ 59 | 'twitter', 60 | 'mixpanel', 61 | 'googleFonts' 62 | ]); 63 | 64 | Then, for example, using Express and [Helmet](https://www.npmjs.com/package/helmet): 65 | 66 | var helmet = require('helmet'); 67 | 68 | app.use(helmet.contentSecurityPolicy({ 69 | directives: policy 70 | })); 71 | 72 | ## Need another API? 73 | 74 | **Add more policies!** Send a pull request to add more policies. Include a reference to an official policy if it exists, or state that there is no official policy if none exists. 75 | 76 | ## I want to steal this and port it to Ruby / Elixir / Python / Java / etc. 77 | 78 | Go for it! Just take `policies.js` (it's just JSON plus comments, hence `.js`) and make sure you regularly update from this project! 79 | 80 | ## Adding custom policies 81 | 82 | You can also create your custom policies and provide them to the API: if `exampleThing` is not provided by this library, you can still define it yourself and use it: 83 | 84 | ```javascript 85 | var exampleThing = { 86 | scriptSrc: ['js.example.com', 'api.example.com'], 87 | imgSrc: ['q.example.com'], 88 | connectSrc: ['api.example.com'], 89 | frameSrc: ['js.example.com'] 90 | } 91 | 92 | cspByAPI(basePolicy, [ 93 | exampleThing, 94 | 'googleFonts' 95 | ]) 96 | ``` 97 | 98 | You should still send a pull request though! 99 | 100 | ## Note 101 | 102 | Some of these are just general notes about CSP, but you'll still find them useful 103 | 104 | ### Avoiding use of `script-src` `unsafe-inline`: 105 | 106 | You will likely need to move the content of inline scripts (` 116 | {{/ serverVars }} 117 | 118 | Then in a script tag on your server: 119 | 120 | var serverVarsElement = document.getElementsByClassName('server-vars')[0] 121 | if ( serverVarsElement ) { 122 | window.serverVars = JSON.parse(serverVarsElement.textContent); 123 | } 124 | 125 | ### Extra meta tag needed for Twitter oembed API 126 | 127 | For **Twitter**, you'll also need this meta tag - see https://dev.twitter.com/web/embedded-tweets/faq: 128 | 129 | 130 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | // Tests. Mocha TDD/assert style. See 2 | // http://visionmedia.github.com/mocha/ 3 | // http://nodejs.org/docs/latest/api/assert.html 4 | 5 | var assert = require('assert') 6 | 7 | var agave = require('agave')('av'); 8 | 9 | var log = console.log.bind(console) 10 | 11 | var makeContentSecurityPolicy = require('../index.js'); 12 | 13 | var CSP_SELF = "'self'"; 14 | var CSP_UNSAFE_EVAL = "'unsafe-eval'"; 15 | var CSP_UNSAFE_INLINE = "'unsafe-inline'"; 16 | 17 | suite('combining policies', function() { 18 | test('returns correct result for common services', function() { 19 | var basePolicy = { 20 | defaultSrc: [CSP_SELF], 21 | scriptSrc: [CSP_SELF], 22 | styleSrc: [CSP_SELF, CSP_UNSAFE_INLINE], 23 | fontSrc: [], 24 | imgSrc: [CSP_SELF, 'data:'], 25 | connectSrc: [CSP_SELF], 26 | frameSrc: [], 27 | reportUri: "/csp-violation", 28 | reportOnly: true 29 | } 30 | 31 | var actual = makeContentSecurityPolicy(basePolicy, ['twitter', 'mixpanel', 'googleFonts', 'typekit', 'stripe', 'ractive']) 32 | 33 | var expected = { 34 | "defaultSrc": [ 35 | "'self'" 36 | ], 37 | "scriptSrc": [ 38 | "'self'", 39 | "'unsafe-eval'", 40 | "analytics.twitter.com", 41 | "api.stripe.com", 42 | "cdn.mxpnl.com", 43 | "cdn.syndication.twimg.com", 44 | "js.stripe.com", 45 | "platform.twitter.com", 46 | "static.ads-twitter.com", 47 | "syndication.twitter.com", 48 | "use.typekit.net" 49 | ], 50 | "styleSrc": [ 51 | "'self'", 52 | "'unsafe-inline'", 53 | "fonts.googleapis.com", 54 | "platform.twitter.com", 55 | "use.typekit.net" 56 | ], 57 | "fontSrc": [ 58 | "data:", 59 | "fonts.googleapis.com", 60 | "fonts.gstatic.com", 61 | "themes.googleusercontent.com", 62 | "use.typekit.net" 63 | ], 64 | "imgSrc": [ 65 | "'self'", 66 | "abs.twimg.com", 67 | "cdn.mxpnl.com", 68 | "data:", 69 | "p.typekit.net", 70 | "pbs.twimg.com", 71 | "platform.twitter.com", 72 | "q.stripe.com", 73 | "syndication.twitter.com" 74 | ], 75 | "connectSrc": [ 76 | "'self'", 77 | "api.mixpanel.com", 78 | "api.stripe.com", 79 | "syndication.twitter.com" 80 | ], 81 | "frameSrc": [ 82 | "js.stripe.com", 83 | "platform.twitter.com", 84 | "syndication.twitter.com" 85 | ], 86 | "reportUri": "/csp-violation", 87 | "reportOnly": true 88 | } 89 | expected.avforEach(function(key) { 90 | if (avkind(expected[key]) === 'Array') { 91 | expected[key] = expected[key].sort(); 92 | } 93 | }) 94 | assert.deepEqual(actual, expected) 95 | }); 96 | 97 | test('throws if non-existent policy specified', function() { 98 | var shouldThrow = function() { 99 | makeContentSecurityPolicy({}, ['unknown-app']) 100 | } 101 | assert.throws(shouldThrow); 102 | }); 103 | 104 | test('remove specific domains when combined with a wildcard', function() { 105 | var basePolicy = { 106 | defaultSrc: [CSP_SELF], 107 | scriptSrc: [CSP_SELF, '*.twitter.com'], 108 | styleSrc: [CSP_SELF, CSP_UNSAFE_INLINE], 109 | fontSrc: [], 110 | imgSrc: [CSP_SELF, 'data:'], 111 | connectSrc: [CSP_SELF], 112 | frameSrc: [], 113 | reportUri: "/csp-violation", 114 | reportOnly: true 115 | } 116 | var actual = makeContentSecurityPolicy(basePolicy, ['twitter']) 117 | var expected = { 118 | "defaultSrc": [ 119 | "'self'" 120 | ], 121 | "scriptSrc": [ 122 | "'self'", 123 | "*.twitter.com", 124 | "cdn.syndication.twimg.com", 125 | "static.ads-twitter.com" 126 | ], 127 | "styleSrc": [ 128 | "'self'", 129 | "'unsafe-inline'", 130 | "platform.twitter.com" 131 | ], 132 | "fontSrc": [], 133 | "imgSrc": [ 134 | "'self'", 135 | "abs.twimg.com", 136 | "data:", 137 | "pbs.twimg.com", 138 | "platform.twitter.com", 139 | "syndication.twitter.com" 140 | ], 141 | "connectSrc": [ 142 | "'self'", 143 | "syndication.twitter.com" 144 | ], 145 | "frameSrc": [ 146 | "platform.twitter.com", 147 | "syndication.twitter.com" 148 | ], 149 | "reportUri": "/csp-violation", 150 | "reportOnly": true 151 | } 152 | assert.deepEqual(actual, expected) 153 | }); 154 | 155 | test('can provide custom policies instead of names', function() { 156 | var basePolicy = { 157 | defaultSrc: [CSP_SELF], 158 | scriptSrc: [CSP_SELF], 159 | styleSrc: [CSP_SELF, CSP_UNSAFE_INLINE], 160 | fontSrc: [], 161 | imgSrc: [CSP_SELF, 'data:'], 162 | connectSrc: [CSP_SELF], 163 | frameSrc: [], 164 | reportUri: "/csp-violation", 165 | reportOnly: true 166 | } 167 | 168 | var stripe = { 169 | scriptSrc: ['js.stripe.com', 'api.stripe.com'], 170 | imgSrc: ['q.stripe.com'], 171 | connectSrc: ['api.stripe.com'], 172 | frameSrc: ['js.stripe.com'] 173 | }; 174 | 175 | var rollbar = { 176 | scriptSrc: ['cdnjs.cloudflare.com'], 177 | connectSrc: ['api.rollbar.com'] 178 | }; 179 | 180 | var before = makeContentSecurityPolicy(basePolicy, ['stripe', 'rollbar']) 181 | var specifiedAsCustomPolicy = makeContentSecurityPolicy(basePolicy, [stripe, rollbar]) 182 | 183 | assert.deepEqual(before, specifiedAsCustomPolicy) 184 | }); 185 | }); 186 | -------------------------------------------------------------------------------- /policies.js: -------------------------------------------------------------------------------- 1 | // CSP uses strings with quotes inside them for special variables. 2 | const CSP_SELF = "'self'", 3 | CSP_UNSAFE_EVAL = "'unsafe-eval'", 4 | CSP_UNSAFE_INLINE = "'unsafe-inline'", 5 | DATA = "data:" 6 | 7 | module.exports = { 8 | 9 | clearbit: { 10 | imgSrc: ['logo.clearbit.com'] 11 | }, 12 | 13 | // Braintree Payments: https://developers.braintreepayments.com/reference/client-reference/javascript/v2/best-practices#using-braintree.js-with-a-content-security-policy 14 | braintree: { 15 | "scriptSrc": [ 16 | "js.braintreegateway.com", 17 | "assets.braintreegateway.com", 18 | "api.braintreegateway.com", 19 | "api.sandbox.braintreegateway.com", 20 | "www.paypalobjects.com", 21 | "client-analytics.sandbox.braintreegateway.com", 22 | "client-analytics.braintreegateway.com" 23 | ], 24 | styleSrc: [CSP_UNSAFE_INLINE], 25 | imgSrc: [ 26 | "assets.braintreegateway.com", 27 | "checkout.paypal.com", 28 | DATA 29 | ], 30 | "frameSrc": [ 31 | "assets.braintreegateway.com", 32 | "c.paypal.com" 33 | ] 34 | }, 35 | 36 | // Google Fonts: no public policy 37 | googleFonts: { 38 | // Unsafe inline needed for script tags 39 | styleSrc: ['fonts.googleapis.com'], 40 | // data://: needed for embedded base64 encoded fonts 41 | fontSrc: ['data:', 'fonts.googleapis.com', 'fonts.gstatic.com','themes.googleusercontent.com'] 42 | }, 43 | 44 | perfectAudience: { 45 | // From Tony at support@perfectaudience.com 46 | // Here are the URLs that we use for scripts that you should be white listing: 47 | "scriptSrc": [ 48 | "pixel.prfct.co", 49 | // Since perfectAudience is a retargeting platform there's lot of third party APIs 50 | "ads.yahoo.com", 51 | "analytics.twitter.com", 52 | "cm.g.doubleclick.net", 53 | "p.univide.com", 54 | "www.facebook.com" 55 | ] 56 | }, 57 | 58 | digiCertSiteSeal: { 59 | "scriptSrc": ["seal.digicert.com"], 60 | "imgSrc": ["seal.digicert.com"] 61 | }, 62 | 63 | googleAnalytics: { 64 | "scriptSrc": [ 65 | "https://ajax.googleapis.com", 66 | "https://www.googleadservices.com", 67 | "https://www.google-analytics.com" 68 | ], 69 | 70 | "connectSrc": [ 71 | "https://www.google-analytics.com" 72 | ], 73 | 74 | "imgSrc": [ 75 | "https://*.googleapis.com", 76 | "https://*.g.doubleclick.net", 77 | "https://www.google.co.in", 78 | "https://www.google.it", 79 | "https://www.google.co.uk", 80 | "https://www.google.de", 81 | "https://www.google.fr", 82 | "https://www.google.ca", 83 | "https://www.google.es", 84 | "https://www.google.com.pk", 85 | "https://www.google.com.tw", 86 | "https://www.google.com.ph", 87 | "https://www.google.com.ua", 88 | "https://www.google.co.kr", 89 | "https://www.google.com", 90 | "https://www.google.com.bd", 91 | "https://www.google.com.bh", 92 | "https://www.google.com.br", 93 | "https://www.google.com.eg", 94 | "https://www.google.nl", 95 | "https://www.google.by", 96 | "https://www.google.co.za", 97 | "https://www.google.fi", 98 | "https://www.google.be", 99 | "https://www.google.co.in", 100 | "https://www.google.com.my", 101 | "https://www.google.ch", 102 | "https://www.google.co.th", 103 | "https://www.google.co.uk", 104 | "https://www.google.cl", 105 | "https://www.google.bg", 106 | "https://www.google.hu", 107 | "https://www.google.com.sa", 108 | "https://www.google.com.sg", 109 | "https://www.google.ie", 110 | "https://www.google.ae", 111 | "https://www.google.dk", 112 | "https://www.google.cz", 113 | "https://www.google.com.mx", 114 | "https://www.google.com.sv", 115 | "https://www.google.co.in", 116 | "https://www.google.se", 117 | "https://www.google.sk", 118 | "https://www.google.com.ar", 119 | "https://www.google.com.uy", 120 | "https://www.google.co.nz", 121 | "https://www.google.co.il", 122 | "https://www.google.com.hk", 123 | "https://www.google.com.vn", 124 | "https://www.google.com.au", 125 | "https://www.google.com.tr", 126 | "https://www.google.co.jp", 127 | "https://www.google.rs", 128 | "https://www.google.ro", 129 | "https://www.google.pl" 130 | ] 131 | }, 132 | 133 | // gravatar No public policy. 134 | gravatar: { 135 | imgSrc: ['s.gravatar.com'] 136 | }, 137 | 138 | // Mixpanel: no public policy 139 | mixpanel: { 140 | scriptSrc: ['cdn.mxpnl.com'], 141 | connectSrc: ['api.mixpanel.com'], 142 | imgSrc: ['cdn.mxpnl.com'] 143 | }, 144 | 145 | // Magic Signup: no public policy 146 | magicSignup: { 147 | scriptSrc: ['magicsignup.com', '*.magicsignup.com'] 148 | }, 149 | 150 | olark: { 151 | // https://www.olark.com/help/csp-support 152 | // But that's missing an actual policy. 153 | // So below is from from actual testing 154 | // 155 | // Eg, api, static, start-1.olark.com. Wildcard because there might be start-2 in future. 156 | scriptSrc: ['*.olark.com', CSP_UNSAFE_INLINE], 157 | connectSrc: ['*.olark.com'], // Always -events.olark.com 158 | styleSrc: [CSP_UNSAFE_INLINE, 'static.olark.com'], 159 | frameSrc: ['static.olark.com'], 160 | imgSrc: ['log.olark.com', 'static.olark.com'], 161 | mediaSrc: ['static.olark.com'] 162 | }, 163 | 164 | // ractive uses eval 165 | ractive: { 166 | scriptSrc: [CSP_UNSAFE_EVAL] 167 | }, 168 | 169 | rollbar: { 170 | scriptSrc: ['cdnjs.cloudflare.com'], 171 | connectSrc: ['api.rollbar.com'] 172 | }, 173 | 174 | // stormpath No public policy. Uses hosted BootStrap and Google fonts 175 | stormpath: { 176 | styleSrc: ['netdna.bootstrapcdn.com', 'fonts.googleapis.com'], 177 | scriptSrc: ['netdna.bootstrapcdn.com', 'ajax.googleapis.com'], 178 | fontSrc: ['netdna.bootstrapcdn.com', 'data:', 'fonts.googleapis.com', 'fonts.gstatic.com'] 179 | }, 180 | 181 | // Stripe: https://support.stripe.com/questions/what-about-pci-dss-3-1 182 | stripe: { 183 | scriptSrc: ['js.stripe.com', 'api.stripe.com'], 184 | imgSrc: ['q.stripe.com'], 185 | connectSrc: ['api.stripe.com'], 186 | frameSrc: ['js.stripe.com'] 187 | }, 188 | 189 | // No public policy 190 | twitter: { 191 | defaultSrc: [CSP_SELF], 192 | scriptSrc: ['static.ads-twitter.com','platform.twitter.com', 'cdn.syndication.twimg.com', 'syndication.twitter.com', 'analytics.twitter.com'], 193 | styleSrc: ['platform.twitter.com'], 194 | imgSrc: ['pbs.twimg.com', 'abs.twimg.com', 'syndication.twitter.com', 'platform.twitter.com'], 195 | connectSrc: ['syndication.twitter.com'], 196 | frameSrc: ['syndication.twitter.com', 'platform.twitter.com'] 197 | }, 198 | 199 | twitterAnalytics: { 200 | imgSrc: ['t.co', 'analytics.twitter.com'] 201 | }, 202 | 203 | // Typekit: see http://help.typekit.com/customer/portal/articles/1265956-content-security-policy-and-typekit 204 | typekit: { 205 | // use.typekit.net: needed for the typekit javascript 206 | scriptSrc: ['use.typekit.net'], 207 | // unsafe inline needed for font events to work 208 | styleSrc: ['use.typekit.net', CSP_UNSAFE_INLINE], 209 | // data://: needed for embedded base64 encoded fonts 210 | // use.typekit.net: needed for externally loaded fonts 211 | fontSrc: ['data:', 'use.typekit.net'], 212 | // p.typekit.net: used for tracking font usage and paying foundries 213 | imgSrc: ['p.typekit.net'] 214 | }, 215 | 216 | vimeo: { 217 | frameSrc: ['player.vimeo.com'] 218 | } 219 | 220 | } 221 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | agave@^2.2.1: 6 | version "2.2.1" 7 | resolved "https://registry.yarnpkg.com/agave/-/agave-2.2.1.tgz#74b14d13d0012d368811b83031f676ae6b69b335" 8 | 9 | balanced-match@^0.4.1: 10 | version "0.4.2" 11 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" 12 | 13 | brace-expansion@^1.1.7: 14 | version "1.1.7" 15 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.7.tgz#3effc3c50e000531fb720eaff80f0ae8ef23cf59" 16 | dependencies: 17 | balanced-match "^0.4.1" 18 | concat-map "0.0.1" 19 | 20 | browser-stdout@1.3.0: 21 | version "1.3.0" 22 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" 23 | 24 | commander@2.9.0: 25 | version "2.9.0" 26 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" 27 | dependencies: 28 | graceful-readlink ">= 1.0.0" 29 | 30 | concat-map@0.0.1: 31 | version "0.0.1" 32 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 33 | 34 | debug@2.6.0: 35 | version "2.6.0" 36 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" 37 | dependencies: 38 | ms "0.7.2" 39 | 40 | diff@3.2.0: 41 | version "3.2.0" 42 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" 43 | 44 | escape-string-regexp@1.0.5: 45 | version "1.0.5" 46 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 47 | 48 | fs.realpath@^1.0.0: 49 | version "1.0.0" 50 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 51 | 52 | glob@7.1.1: 53 | version "7.1.1" 54 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" 55 | dependencies: 56 | fs.realpath "^1.0.0" 57 | inflight "^1.0.4" 58 | inherits "2" 59 | minimatch "^3.0.2" 60 | once "^1.3.0" 61 | path-is-absolute "^1.0.0" 62 | 63 | "graceful-readlink@>= 1.0.0": 64 | version "1.0.1" 65 | resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" 66 | 67 | growl@1.9.2: 68 | version "1.9.2" 69 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" 70 | 71 | has-flag@^1.0.0: 72 | version "1.0.0" 73 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" 74 | 75 | inflight@^1.0.4: 76 | version "1.0.6" 77 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 78 | dependencies: 79 | once "^1.3.0" 80 | wrappy "1" 81 | 82 | inherits@2: 83 | version "2.0.3" 84 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 85 | 86 | json3@3.3.2: 87 | version "3.3.2" 88 | resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" 89 | 90 | lodash._baseassign@^3.0.0: 91 | version "3.2.0" 92 | resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" 93 | dependencies: 94 | lodash._basecopy "^3.0.0" 95 | lodash.keys "^3.0.0" 96 | 97 | lodash._basecopy@^3.0.0: 98 | version "3.0.1" 99 | resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" 100 | 101 | lodash._basecreate@^3.0.0: 102 | version "3.0.3" 103 | resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821" 104 | 105 | lodash._getnative@^3.0.0: 106 | version "3.9.1" 107 | resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" 108 | 109 | lodash._isiterateecall@^3.0.0: 110 | version "3.0.9" 111 | resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" 112 | 113 | lodash.create@3.1.1: 114 | version "3.1.1" 115 | resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7" 116 | dependencies: 117 | lodash._baseassign "^3.0.0" 118 | lodash._basecreate "^3.0.0" 119 | lodash._isiterateecall "^3.0.0" 120 | 121 | lodash.isarguments@^3.0.0: 122 | version "3.1.0" 123 | resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" 124 | 125 | lodash.isarray@^3.0.0: 126 | version "3.0.4" 127 | resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" 128 | 129 | lodash.keys@^3.0.0: 130 | version "3.1.2" 131 | resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" 132 | dependencies: 133 | lodash._getnative "^3.0.0" 134 | lodash.isarguments "^3.0.0" 135 | lodash.isarray "^3.0.0" 136 | 137 | lodash@^3.10.1: 138 | version "3.10.1" 139 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" 140 | 141 | minimatch@^3.0.2: 142 | version "3.0.4" 143 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 144 | dependencies: 145 | brace-expansion "^1.1.7" 146 | 147 | minimist@0.0.8: 148 | version "0.0.8" 149 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 150 | 151 | mkdirp@0.5.1: 152 | version "0.5.1" 153 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 154 | dependencies: 155 | minimist "0.0.8" 156 | 157 | mocha@^3.3.0: 158 | version "3.3.0" 159 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.3.0.tgz#d29b7428d3f52c82e2e65df1ecb7064e1aabbfb5" 160 | dependencies: 161 | browser-stdout "1.3.0" 162 | commander "2.9.0" 163 | debug "2.6.0" 164 | diff "3.2.0" 165 | escape-string-regexp "1.0.5" 166 | glob "7.1.1" 167 | growl "1.9.2" 168 | json3 "3.3.2" 169 | lodash.create "3.1.1" 170 | mkdirp "0.5.1" 171 | supports-color "3.1.2" 172 | 173 | ms@0.7.2: 174 | version "0.7.2" 175 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" 176 | 177 | once@^1.3.0: 178 | version "1.4.0" 179 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 180 | dependencies: 181 | wrappy "1" 182 | 183 | path-is-absolute@^1.0.0: 184 | version "1.0.1" 185 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 186 | 187 | supports-color@3.1.2: 188 | version "3.1.2" 189 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" 190 | dependencies: 191 | has-flag "^1.0.0" 192 | 193 | wrappy@1: 194 | version "1.0.2" 195 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 196 | --------------------------------------------------------------------------------