├── .gitignore ├── CSPValidator.py ├── CSPValidator.sublime-settings ├── Default (Linux).sublime-keymap ├── Default (OSX).sublime-keymap ├── Default (Windows).sublime-keymap ├── LICENSE ├── Main.sublime-menu └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | CSPValidator.pyc 2 | -------------------------------------------------------------------------------- /CSPValidator.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | import re 4 | 5 | 6 | class CSPRule(): 7 | """ Represents a validation rule """ 8 | rule = None 9 | message = None 10 | setting = None 11 | 12 | def __init__(self, rule, message, setting=None): 13 | self.rule = rule 14 | self.message = message 15 | self.setting = setting 16 | 17 | 18 | class CSPError: 19 | """ Represents an error """ 20 | region = None 21 | message = '' 22 | 23 | def __init__(self, region, message): 24 | self.region = region 25 | self.message = message 26 | 27 | 28 | class CSPValidator(): 29 | """ Runs the validation rules """ 30 | 31 | rules = [ 32 | 33 | # Matches on src attributes with an http[s] protocol 34 | CSPRule( 35 | "<(img|script).*?\ssrc\s?=\s?[\"']+http[^\"\']*[\"\']?", 36 | "External resources are not allowed", 37 | "csp_chromeapps" 38 | ), 39 | 40 | # Matches on src attributes with an http[s] protocol 41 | CSPRule( 42 | "]*>[^<]+?" + 52 | 53 | # Non-whitespace chars that are _not_ closing the tag 54 | "[^\s<]+?" + 55 | 56 | # Now non-greedy fill to the end of the tag 57 | ".*?", 58 | 59 | "Inline scripts are not allowed" 60 | ), 61 | 62 | # Matches on eval / new Function 63 | CSPRule( 64 | "eval|new Function", 65 | "Code creation from strings, e.g. eval / new Function not allowed" 66 | ), 67 | 68 | # Matches on eval / new Function 69 | CSPRule( 70 | "setTimeout\s?\(\"[^\"]*\"", 71 | "Code creation from strings, e.g. setTimeout(\"string\") is not allowed" 72 | ), 73 | 74 | # Matches on on{event} 75 | CSPRule( 76 | "<.*?\son[^>]*?>", 77 | "Event handlers should be added from an external src file" 78 | ), 79 | 80 | # Matches external resources in CSS 81 | CSPRule( 82 | "url\(\"?(?:https?:)?//[^\)]*\)", 83 | "External resources are not allowed", 84 | "csp_chromeapps" 85 | ), 86 | 87 | # Matches hrefs with a javascript: url 88 | CSPRule( 89 | "<.*?href.*?javascript:.*?>", 90 | "Inline JavaScript calls are not allowed", 91 | "csp_chromeapps" 92 | ) 93 | ] 94 | 95 | def get_view_contents(self, view): 96 | return view.substr(sublime.Region(0, view.size())) 97 | 98 | def validate_contents(self, view): 99 | errors = [] 100 | 101 | for rule in self.rules: 102 | # Check for any specific settings that govern the 103 | # assertion of this particular rule 104 | if(rule.setting == None or view.settings().get(rule.setting) == 1): 105 | matches = view.find_all(rule.rule, sublime.IGNORECASE) 106 | for match in matches: 107 | errors.append( 108 | CSPError(match, rule.message) 109 | ) 110 | 111 | return errors 112 | 113 | 114 | class CSPValidatorCommand(sublime_plugin.EventListener): 115 | """ Main Validator Class """ 116 | errors = None 117 | pluginSettings = None 118 | validator = CSPValidator() 119 | 120 | # these are the default settings. They are overridden and 121 | # documented in the GLShaderValidator.sublime-settings file 122 | DEFAULT_SETTINGS = { 123 | "csp_enabled": 1, 124 | "csp_chromeapps": 0 125 | } 126 | 127 | def __init__(self): 128 | """ Startup """ 129 | 130 | def clear_settings(self): 131 | """ Resets the settings value """ 132 | for window in sublime.windows(): 133 | for view in window.views(): 134 | if view.settings().get('csp_configured') != None: 135 | view.settings().set('csp_configured', None) 136 | 137 | def apply_settings(self, view): 138 | """ Loads in and applies the settings file """ 139 | 140 | # Lazy load in the settings file 141 | if self.pluginSettings == None: 142 | self.pluginSettings = sublime.load_settings(__name__ + ".sublime-settings") 143 | self.pluginSettings.clear_on_change('csp_validator') 144 | self.pluginSettings.add_on_change('csp_validator', self.clear_settings) 145 | 146 | # Only configure this view if it's not been done before 147 | if view.settings().get('csp_configured') == None: 148 | 149 | view.settings().set('csp_configured', True) 150 | 151 | # Go through the default settings 152 | for setting in self.DEFAULT_SETTINGS: 153 | 154 | # set the value 155 | settingValue = self.DEFAULT_SETTINGS[setting] 156 | 157 | # check if the user has overwritten the value 158 | # and switch to that instead 159 | if self.pluginSettings.get(setting) != None: 160 | settingValue = self.pluginSettings.get(setting) 161 | 162 | view.settings().set(setting, settingValue) 163 | 164 | def clear_errors(self, view): 165 | """ Removes any errors """ 166 | view.erase_regions('cspvalidator_errors') 167 | 168 | def is_valid_file_type(self, view): 169 | """ Checks that the file is worth checking """ 170 | syntax = view.settings().get('syntax') 171 | isValidFile = False 172 | if syntax != None: 173 | isValidFile = re.search('html|javascript|css', syntax, flags=re.IGNORECASE) != None 174 | return isValidFile 175 | 176 | def show_errors(self, view): 177 | """ Passes over the array of errors and adds outlines """ 178 | 179 | # Go through the errors that came back 180 | errorRegions = [] 181 | for error in self.errors: 182 | errorRegions.append(error.region) 183 | 184 | # Put an outline around each one and a dot on the line 185 | view.add_regions( 186 | 'cspvalidator_errors', 187 | errorRegions, 188 | 'cspvalidation_error', 189 | 'dot', 190 | sublime.DRAW_OUTLINED 191 | ) 192 | 193 | def on_selection_modified(self, view): 194 | """ Shows a status message for an error region """ 195 | 196 | view.erase_status('cspvalidation_error') 197 | 198 | # If we have errors just locate 199 | # the first one and go with that for the status 200 | if self.errors != None: 201 | for sel in view.sel(): 202 | for error in self.errors: 203 | if error.region.contains(sel): 204 | view.set_status('cspvalidation_error', 205 | error.message) 206 | return 207 | 208 | def on_load(self, view): 209 | """ File loaded """ 210 | self.run_validator(view) 211 | 212 | def on_activated(self, view): 213 | """ File activated """ 214 | self.run_validator(view) 215 | 216 | def on_post_save(self, view): 217 | """ File saved """ 218 | self.run_validator(view) 219 | 220 | def run_validator_all_views(self): 221 | for window in sublime.windows(): 222 | for view in window.views(): 223 | self.run_validator(view) 224 | 225 | def run_validator(self, view): 226 | """ Runs a validation pass """ 227 | 228 | # clear the last run 229 | view.erase_status('cspvalidation_error') 230 | 231 | # set up the settings if necessary 232 | self.apply_settings(view) 233 | 234 | # early return if they have disabled the linter 235 | if view.settings().get('csp_enabled') == 0: 236 | self.clear_errors(view) 237 | return 238 | 239 | # early return for anything not using the correct syntax 240 | if not self.is_valid_file_type(view): 241 | return 242 | 243 | # Clear the last set of errors 244 | self.clear_errors 245 | 246 | # Get the file and send to the validator 247 | self.errors = self.validator.validate_contents(view) 248 | if self.errors != None: 249 | self.show_errors(view) 250 | 251 | 252 | class ContentSecurityPolicyToggleCommand(sublime_plugin.ApplicationCommand): 253 | """ Toggles the CSP validation on and off """ 254 | def run(self): 255 | # Get the settings for the plugin 256 | currentSettings = sublime.load_settings(__name__ + '.sublime-settings') 257 | setting = "csp_enabled" 258 | 259 | # Invert the values 260 | currentSettings.set(setting, 1 - currentSettings.get(setting)) 261 | 262 | # Now save 263 | sublime.save_settings(__name__ + '.sublime-settings') 264 | 265 | # And force a clear out and re-run 266 | validator = CSPValidatorCommand() 267 | validator.clear_settings() 268 | validator.run_validator_all_views() 269 | -------------------------------------------------------------------------------- /CSPValidator.sublime-settings: -------------------------------------------------------------------------------- 1 | /* 2 | CSP Validator settings 3 | */ 4 | { 5 | /* 6 | Sets whether the plugin runs: 7 | 8 | 0 = Validation disabled 9 | 1 = Validation enabled [load, save, activation] 10 | 11 | */ 12 | "csp_enabled": 1, 13 | 14 | /* 15 | Sets whether Chrome Apps specific validation is required. This 16 | mainly affects the loading in of remote assets, which is disallowed 17 | 18 | 0 = Chrome Apps-specific validations disabled 19 | 1 = Chrome Apps-specific validations enabled [load, save, activation] 20 | 21 | */ 22 | "csp_chromeapps": 1 23 | } 24 | -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": ["ctrl+alt+shift+c"], 4 | "command": "content_security_policy_toggle" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": ["ctrl+alt+shift+c"], 4 | "command": "content_security_policy_toggle" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": ["ctrl+alt+shift+c"], 4 | "command": "content_security_policy_toggle" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012 Google Inc. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Preferences", 4 | "id": "preferences", 5 | "children": [ 6 | { 7 | "caption": "Package Settings", 8 | "id": "package-settings", 9 | "children": [ 10 | { 11 | "caption": "CSP Validator", 12 | "id": "cspvalidator", 13 | "children": [ 14 | { 15 | "command": "open_file", 16 | "args": { 17 | "file": "${packages}/CSP-Validator/README.md" 18 | }, 19 | "caption": "README" 20 | }, 21 | { 22 | "command": "open_file", 23 | "args": { 24 | "file": "${packages}/CSP-Validator/LICENSE" 25 | }, 26 | "caption": "LICENSE" 27 | }, 28 | { 29 | "caption": "-" 30 | }, 31 | { 32 | "command": "open_file", 33 | "args": { 34 | "file": "${packages}/CSP-Validator/CSPValidator.sublime-settings" 35 | }, 36 | "caption": "Settings - Default" 37 | } 38 | ] 39 | } 40 | ] 41 | } 42 | ] 43 | } 44 | ] 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSP Validator 2 | 3 | ![image](http://aerotwist.com/cspvalidator/screenshot.png) 4 | 5 | This is a [Sublime Text 2](http://www.sublimetext.com/) plugin that checks your 6 | JavaScript, HTML and CSS for potential [Content Security Policy](https://developer.chrome.com/extensions/contentSecurityPolicy.html) issues. If you're new to Content Security Policy 7 | there is, in fact, [an HTML5 Rocks article for that](http://www.html5rocks.com/en/tutorials/security/content-security-policy/)! 8 | 9 | Right now the plugin checks for: 10 | 11 | * Inline scripts 12 | * Images and scripts with src attributes with http(s) protocols 13 | * Use of eval or new Function 14 | * setTimeout with a string param (this is only explicit usage of a string, not if it's passed as a variable) 15 | * Attempting to load resources in CSS with http(s) protocols 16 | 17 | ## Installation 18 | 19 | Right now you need to clone this repo into your packages folder 20 | (typically ~/Library/Application Support/Sublime Text 2/Packages). 21 | 22 | ``` 23 | cd ~/Library/Application\ Support/Sublime\ Text\ 2/Packages 24 | git clone git://github.com/paullewis/CSP-Validator.git 25 | ``` 26 | 27 | _Please note: this is only an alpha release. Once all the issues are ironed out I'll request 28 | to be added to Package Control._ 29 | 30 | ## Usage 31 | 32 | Just code away and all being well you will receive warnings as the plugin finds 33 | them. If for any reason you want to disable the warnings you can use *Ctrl + Option + Shift + C* (or Alt on PC instead of Option) to disable the plugin. 34 | --------------------------------------------------------------------------------