Index: wp-includes/functions.php
===================================================================
--- wp-includes/functions.php	(revision 14075)
+++ wp-includes/functions.php	(working copy)
@@ -1587,6 +1587,23 @@ function get_status_header_desc( $code ) {
 }
 
 /**
+ * Gets the Content Security Policy if specified by the admin.
+ * If CSP is not enabled, then the return value will be false.
+ *
+ * @return string Content Security Policy header value
+ */
+function get_csp() {
+  if (!get_option("csp_enabled"))
+    return false;
+  $policy = get_option("csp_value");
+  // use default policy "allow 'self'" if user has not configured a policy
+  if ( !strlen(trim($policy)) )
+    return "allow 'self'";
+  else
+    return $policy;
+}
+
+/**
  * Set HTTP status header.
  *
  * @since 2.0.0
Index: wp-includes/classes.php
===================================================================
--- wp-includes/classes.php	(revision 14075)
+++ wp-includes/classes.php	(working copy)
@@ -317,6 +317,11 @@ class WP {
 	 */
 	function send_headers() {
 		$headers = array('X-Pingback' => get_bloginfo('pingback_url'));
+    // Content Security Policy header
+    $csp = get_csp();
+    if ($csp)
+      $headers["X-Content-Security-Policy"] = $csp;
+
 		$status = null;
 		$exit_required = false;
 
Index: wp-includes/script-loader.php
===================================================================
--- wp-includes/script-loader.php	(revision 14075)
+++ wp-includes/script-loader.php	(working copy)
@@ -473,6 +473,7 @@ function wp_default_styles( &$styles ) {
 	$styles->add( 'jcrop', '/wp-includes/js/jcrop/jquery.Jcrop.css', array(), '0.9.8' );
 	$styles->add( 'imgareaselect', '/wp-includes/js/imgareaselect/imgareaselect.css', array(), '0.9.1' );
 	$styles->add( 'nav-menu', "/wp-admin/css/nav-menu$suffix.css", array(), '20100322' );
+	$styles->add( 'security', "/wp-admin/css/security.css", array(), '20100412' );
 
 	foreach ( $rtl_styles as $rtl_style ) {
 		$styles->add_data( $rtl_style, 'rtl', true );
Index: wp-admin/menu.php.rej
===================================================================
--- wp-admin/menu.php.rej	(revision 0)
+++ wp-admin/menu.php.rej	(revision 0)
@@ -0,0 +1,22 @@
+*************** $menu[80] = array( __('Settings'), 'manage_options
+*** 158,166 ****
+  	$submenu['options-general.php'][25] = array(__('Discussion'), 'manage_options', 'options-discussion.php');
+  	$submenu['options-general.php'][30] = array(__('Media'), 'manage_options', 'options-media.php');
+  	$submenu['options-general.php'][35] = array(__('Privacy'), 'manage_options', 'options-privacy.php');
+- 	$submenu['options-general.php'][40] = array(__('Permalinks'), 'manage_options', 'options-permalink.php');
+  	if ( is_super_admin() )
+- 		$submenu['options-general.php'][45] = array(__('Miscellaneous'), 'manage_options', 'options-misc.php');
+  
+  $_wp_last_utility_menu = 80; // The index of the last top-level menu in the utility menu group
+  
+--- 158,167 ----
+  	$submenu['options-general.php'][25] = array(__('Discussion'), 'manage_options', 'options-discussion.php');
+  	$submenu['options-general.php'][30] = array(__('Media'), 'manage_options', 'options-media.php');
+  	$submenu['options-general.php'][35] = array(__('Privacy'), 'manage_options', 'options-privacy.php');
++ 	$submenu['options-general.php'][40] = array(__('Security'), 'manage_options', 'options-security.php');
++ 	$submenu['options-general.php'][45] = array(__('Permalinks'), 'manage_options', 'options-permalink.php');
+  	if ( is_super_admin() )
++ 		$submenu['options-general.php'][50] = array(__('Miscellaneous'), 'manage_options', 'options-misc.php');
+  
+  $_wp_last_utility_menu = 80; // The index of the last top-level menu in the utility menu group
+  
Index: wp-admin/js/options-security.dev.js
===================================================================
--- wp-admin/js/options-security.dev.js	(revision 0)
+++ wp-admin/js/options-security.dev.js	(revision 0)
@@ -0,0 +1,505 @@
+/*
+ * Script for Content Security Policy admin page
+ * Brandon Sterne <bsterne@mozilla.com> 
+ *
+ * Uses Mark Perkins' jQuery URL Parser plugin documented here
+ * http://projects.allmarkedup.com/jquery_url_parser/
+ */
+
+// keep a list of hosts for each type of content served on this blog
+var sources = {
+  'images': {}, 'media': {}, 'script': {}, 'object': {},
+  'frame': {},  'font': {},  'style': {}
+};
+
+// store mappings between content-types and CSP directive names
+var directives = {
+  'images': 'img-src', 'media': 'media-src', 'script': 'script-src',
+  'object': 'object-src', 'frame': 'frame-src',  'font': 'font-src',
+  'style': 'style-src'
+};
+// allows us to look up directive mappings in either direction
+directives.invert = function() {
+  var inverted = {};
+  for (var prop in this) {
+    if (prop != "invert") inverted[this[prop]] = prop;
+  }
+  return inverted;
+}
+
+// Douglas Crockford's trim() implementation
+String.prototype.trim = function () {
+  return this.replace(/^\s+|\s+$/g, "");
+};
+
+// return a word with the first letter capitalized
+function capWord(w) {
+  return w.charAt(0).toUpperCase() + w.slice(1);
+}
+
+// return whether or not an object has no properties
+function objIsEmpty(obj) {
+  for (var prop in obj) {
+    if (obj.hasOwnProperty(prop))
+      return false;
+  }
+  return true;
+}
+
+// show debugging info
+var DEBUG = false;
+
+// for debugging: return a pretty-printed version of the content sources
+function display(sources) {
+  var s = "{\n";
+  for (type in sources) {
+    s += "  "+type+": [";
+    for (host in sources[type]) {
+      s += host+", ";
+    }
+    s += "],\n";
+  }
+  s += "}";
+  return s;
+}
+
+// page loaded
+jQuery(document).ready(function($) {
+  // if there is an existing policy saved we will display it visually
+  var policy = $("#csp_value").val();
+  // CSP is enabled, but not customized.  The default policy is to allow content
+  // only from this site
+  if (policy == "allow 'self';") {
+    var myHost = jQuery.url.setUrl($(this).attr("src")).attr("host");
+    // show non-editable policy representation.  we don't want them to be able
+    // to remove "allow 'self'" (at least through the visual policy editor).
+    showVisualPolicy({"allowing":{"'self'":null}}, "csp_display", false);
+  }
+  // policy has been customized by the user
+  else if (policy.length) {
+    // parse the CSP syntax into our JS object format
+    var myDirs = policy.split(";");  // current CSP directives
+    // directive-to-readable-name mappings
+    var invDirectives = directives.invert();
+    for (var i = 0 ; i < myDirs.length ; i++) {
+      var dirParts = myDirs[i].trim().split(/[\s]+/);
+      // only process directives we know about
+      if (dirParts[0] in invDirectives) {
+        // add hosts allowed for this directive
+        for (var j = 1 ; j < dirParts.length ; j++)
+          sources[ invDirectives[dirParts[0]] ][dirParts[j]] = null;
+      }
+    }
+    if (DEBUG) $("#log").text(display(sources));    
+    // show editable list of policy sources
+    showVisualPolicy(sources, "csp_display", true);
+  }
+  // no CSP set
+  else {
+    $("#csp_display").html("<p>&nbsp;</p>");
+  }
+
+  // discard changes to the policy settings by reloading the page
+  $("#csp_discard").click( function() {
+    window.location.reload(true);
+  });
+
+  // show/hide the advanced panel for manual editing
+  $("#csp_toggle_advanced").click( function() {
+    if ($(this).text() == "Show Advanced") {
+      $("#csp_advanced").show();
+      $(this).text("Hide Advanced");
+    }
+    else {
+      $("#csp_advanced").hide();
+      $(this).text("Show Advanced");
+    }
+  });
+
+  // fetch the front page of the blog to analyze for Content Security
+  // Policy suggestion
+  $("#suggestpolicy").click( function() {
+    $("#analyze").attr("src", location.href.replace(/\/[^\/]+\/[^\/]+$/,""));
+    // show throbber while analyzing
+    $("#content-loading").css("visibility", "visible");
+    return false;
+  });
+
+  // when "Enable CSP" preference is toggeld, enable "Discard Changes" button
+  $("#csp_enabled").click( function() {
+    $("#csp_discard").show();
+    // if CSP is enabled, ensure that the source list contains at least
+    // the site itself, since "allow 'self'" is the default policy
+    if ($(this).attr("checked")) {
+      var sourcesIsEmpty = true;
+      for (type in sources) {
+        if (!objIsEmpty(sources[type])) {
+          sourcesIsEmpty = false;
+          break;
+        }
+      }
+      // show non-editable policy representation.  we don't want them to be able
+      // to remove "allow 'self'" (at least through the visual policy editor).
+      if (sourcesIsEmpty)
+        showVisualPolicy({"allowing":{"'self'":null}}, "csp_display", false);
+    }
+  });
+
+  // once the front page has loaded, examine its content for policy suggestions
+  $("#analyze").load(function() {
+    // don't examine until we've loaded a page in the frame
+    if (!$(this).attr("src"))
+      return false;
+
+    // use 'self' for content from this host
+    var myHost = jQuery.url.setUrl($(this).attr("src")).attr("host");
+
+    /* images */
+    // XXX bsterne - nearly every blog I've looked at sources images from
+    // all over the place.  It's potentially confusing to users to show a list
+    // containing every site they get images from.  For now, will allow
+    // images from anywhere.
+    sources.images["*"] = null;
+    /*
+    $.each($(this).contents().find("img"), function() {
+      var host = jQuery.url.setUrl($(this).attr("src")).attr("host");
+      // relative URL, use 'self'
+      if (host == null) {
+        if (!(host in sources.images))
+          sources.images["'self'"] = null;
+      }
+      // absolute URL, store the hostname
+      else {
+        if (!(host in sources.images))
+          sources.images[host] = null;
+      }
+    });
+    */
+
+    /* media: <video> and <audio> */
+    $.each($(this).contents().find("video,audio"), function() {
+      var host = jQuery.url.setUrl($(this).attr("src")).attr("host");
+      // relative URL, use 'self'
+      if (host == null) {
+        if (!(host in sources.media))
+          sources.media["'self'"] = null;
+      }
+      // absolute URL, store the hostname
+      else {
+        if (host == myHost)
+          host = "'self'";
+        if (!(host in sources.media))
+          sources.media[host] = null;
+      }
+    });
+
+    /* external script resources */
+    $.each($(this).contents().find("script"), function() {
+      var host = jQuery.url.setUrl($(this).attr("src")).attr("host");
+      // relative URL, use 'self'
+      if (host == null) {
+        if (!(host in sources.script))
+          sources.script["'self'"] = null;
+      }
+      // absolute URL, store the hostname
+      else {
+        if (host == myHost)
+          host = "'self'";
+        if (!(host in sources.script))
+          sources.script[host] = null;
+      }
+    });
+
+    /* <object>, <applet>, <embed> */
+    // object, applet
+    // http://www.w3.org/TR/1999/REC-html401-19991224/struct/objects.html#h-13.3
+    $.each($(this).contents().find("object,applet"), function() {
+      // codebase: base URI for classid, data, archive attrs
+      var codebase = $(this).attr("codebase");
+      var host = jQuery.url.setUrl(codebase).attr("host");
+      // relative URL, use 'self'
+      if (host == null) {
+        if (!(host in sources.object))
+          sources.object["'self'"] = null;
+      }
+      // absolute URL, store the hostname
+      else {
+        if (host == myHost)
+          host = "'self'";
+        if (!(host in sources.object))
+          sources.object[host] = null;
+      }
+
+      // classid: location of an object's implementation.
+      // XXX bsterne - spec says this is a URI, but in the wild this usually
+      // references a COM registry ID. For now, skipping this URL for any
+      // non-data-returning protocols, e.g. clsid:.
+      var classid = $(this).attr("classid");  // applet won't have this
+      var protocol = jQuery.url.setUrl(classid).attr("protocol");
+      if (jQuery.inArray(protocol, ["http", "https", "ftp", null]) != -1) {
+        host = jQuery.url.setUrl(classid).attr("host");
+        // relative URL, use 'self'
+        if (host == null) {
+          if (!(host in sources.object))
+            sources.object["'self'"] = null;
+        }
+        // absolute URL, store the hostname
+        else {
+          if (host == myHost)
+            host = "'self'";
+          if (!(host in sources.object))
+            sources.object[host] = null;
+        }
+      }
+
+      // data: object's or applet's location
+      var data = $(this).attr("data");  // applet won't have this
+      host = jQuery.url.setUrl(data).attr("host");
+      // relative URL, use 'self'
+      if (host == null) {
+        if (!(host in sources.object))
+          sources.object["'self'"] = null;
+      }
+      // absolute URL, store the hostname
+      else {
+        if (host == myHost)
+          host = "'self'";
+        if (!(host in sources.object))
+          sources.object[host] = null;
+      }
+
+      // archive: space-separated list of URIs of relevant resources
+      var archive = $(this).attr("archive");
+      var hosts = archive.split(" ");
+      $.each(hosts, function() {
+        host = jQuery.url.setUrl(this).attr("host");
+        // relative URL, use 'self'
+        if (host == null) {
+          if (!(host in sources.object))
+            sources.object["'self'"] = null;
+        }
+        // absolute URL, store the hostname
+        else {
+          if (host == myHost)
+            host = "'self'";
+          if (!(host in sources.object))
+            sources.object[host] = null;
+        }
+      });
+    });
+
+    // embed
+    $.each($(this).contents().find("embed"), function() {
+      var host = jQuery.url.setUrl($(this).attr("src")).attr("host");
+      // relative URL, use 'self'
+      if (host == null) {
+        if (!(host in sources.object))
+          sources.object["'self'"] = null;
+      }
+      // absolute URL, store the hostname
+      else {
+        if (host == myHost)
+          host = "'self'";
+        if (!(host in sources.object))
+          sources.object[host] = null;
+      }
+    });
+
+    // frame, iframe
+    $.each($(this).contents().find("frame,iframe"), function() {
+      var host = jQuery.url.setUrl($(this).attr("src")).attr("host");
+      // relative URL, use 'self'
+      if (host == null) {
+        if (!(host in sources.frame))
+          sources.frame["'self'"] = null;
+      }
+      // absolute URL, store the hostname
+      else {
+        if (host == myHost)
+          host = "'self'";
+        if (!(host in sources.frame))
+          sources.frame[host] = null;
+      }
+    });
+
+    // @font-face (downloadable fonts)
+    var stylesheets = {};
+    try {
+      var stylesheets = this.contentDocument.styleSheets;
+    }
+    // XXX bsterne - parsing style rules in IE is hard :-(
+    // see http://www.quirksmode.org/dom/w3c_css.html
+    catch (e) {}
+
+    for (var i = 0 ; i < stylesheets.length ; i++) {
+      // XXX bsterne - apparently, cross-site stylesheets' rules are subject to
+      // same-origin.  We'll only be able to make font policy reccommendations
+      // based on same-site stylesheets.
+      var rules = {};
+      try {
+        // Firefox, Chrome, Safari, etc.
+        rules = stylesheets[i].cssRules;
+        // Internet Explorer would use .rules if we can add support
+        // rules = stylesheets[i].rules;
+      }
+      catch (e) { // probably a cross-site stylesheet which we can't read
+        continue;
+      }
+      // XXX bsterne - Chrome can return null for cssRules but not throw
+      if (!rules) {
+        continue;
+      }
+      // search stylesheet rules for @font-face
+      for (var j = 0 ; j < rules.length ; j++) {
+        if (rules[j].type == rules[j].FONT_FACE_RULE) {
+          var src = rules[j].style.getPropertyValue("src");
+          if (src) {
+            // remove url() wrapper from font-face src
+            // FIXME - get rid of slashes (working around js-mode indent bug)
+            var url = src.replace(/^url[\'"]*/, "").replace(/[\'"]*\)$/, "");
+            var host = jQuery.url.setUrl(url).attr("host");
+            // relative URL, use 'self'
+            if (host == null) {
+              if (!(host in sources.font))
+                sources.font["'self'"] = null;
+            }
+            // absolute URL, store the hostname
+            else {
+              if (host == myHost)
+                host = "'self'";
+              if (!(host in sources.font))
+                sources.font[host] = null;
+            }
+          }
+        }
+      }
+    }
+
+    // external stylesheets
+    $.each($(this).contents().find("link[rel='stylesheet']"), function() {
+      var host = jQuery.url.setUrl($(this).attr("href")).attr("host");
+      // relative URL, use 'self'
+      if (host == null) {
+        if (!(host in sources.style))
+          sources.style["'self'"] = null;
+      }
+      // absolute URL, store the hostname
+      else {
+        if (host == myHost)
+          host = "'self'";
+        if (!(host in sources.style))
+          sources.style[host] = null;
+      }
+    });    
+
+    // turn off throbber
+    $("#content-loading").css("visibility", "hidden");
+
+    // display a reccommended policy that the user can edit, then accept or reject
+    showVisualPolicy(sources, "csp_display", true);
+    // changes were made, enable "Discard Changes" button and change
+    // border of "Trusted Sites" panel
+    $("#csp_discard").show();
+    $("#csp_display").css("border-style", "dashed").css("border-width","2px");
+  });
+
+  // display a visual representation of the policy defined in a source list
+  // inside the specified DOM element.  If |editable| is passed in as true,
+  // the printed list will be interactive
+  function showVisualPolicy(sourceList, elementID, editable) {
+    if(DEBUG) $("#log").text(display(sourceList))
+    $("#"+elementID).empty();
+    var myHost = jQuery.url.setUrl($(this).attr("src")).attr("host");
+    for (type in sourceList) {
+      var div = document.createElement("div");
+      // skip content types which have no sources
+      if (objIsEmpty(sourceList[type]))
+        continue;
+      $(div).addClass("feature-name").text(capWord(type));
+      $("#"+elementID).append(div);
+      var ol = document.createElement("ol");
+      $(ol).addClass("feature-group");
+      for (host in sourceList[type]) {
+        var li = document.createElement("li");
+        $(li).attr("host", host);
+        $(li).attr("ctype", type); // content type
+        // if list is editable, permit the user to remove a source from the
+        // suggested source list or add it back in
+        if (editable) {
+          $(li).click(function() {
+            // changes were made, enable "Discard Changes" button and change
+            // border of "Trusted Sites" panel
+            $("#csp_discard").show();
+            $("#csp_display").css("border-style", "dashed").css("border-width",
+                                                                "2px");
+            // re-enable a previously removed source
+            if ($(this).css("text-decoration") == "line-through") {
+              $(this).css("text-decoration", "none");
+              //sourceList[this.type][this.host] = null;
+              sourceList[$(this).attr("ctype")][$(this).attr("host")] = null;
+            }
+            // remove a source from the list of suggestions
+            else {
+              $(this).css("text-decoration", "line-through");
+              //delete sourceList[this.type][this.host];
+              delete sourceList[$(this).attr("ctype")][$(this).attr("host")];
+            }
+            if (DEBUG) $("#log").text(display(sourceList));
+          });
+        }
+        if (host == "*") {
+          var displayName = "Everyone";
+          var faviconUrl = "images/everyone.gif";
+        }
+        else if (host == "'self'") {
+          var displayName = myHost;
+          var faviconUrl = "http://" + myHost + "/favicon.ico";
+        }
+        else {
+          var displayName = host;
+          var faviconUrl = "http://" + host + "/favicon.ico";
+        }
+        $(li).html("<img src=\"" + faviconUrl + "\" class=\" csp-trusted\"" +
+                   " onerror=\"this.parentNode.removeChild(this)\">");
+        if (editable)
+          $(li).append("<label>" + displayName + "</label>");
+        else
+          $(li).append(displayName);
+        $(ol).append(li);
+      }
+      $("#"+elementID).append(ol).append("<br class=\"clear\">");
+    }
+    // show a message describing how to treat the list of sources
+    $("#csp_verify_text").show();
+  }
+
+  // turn a list of sources into proper CSP syntax
+  function generatePolicyFromSources(sourceList) {
+    var policy = "allow 'self'; ";
+    for (type in sourceList) {
+      // skip types which have no sources specified
+      if (objIsEmpty(sourceList[type]))
+        continue;
+      policy += directives[type] + " ";
+      for (host in sourceList[type]) {
+        policy += host+" ";
+      }
+      policy += "; ";
+    }
+    return policy;
+  }
+  
+  // once the user has verified the list of sources they want to allow, turn the
+  // list of hosts-per-content-type into proper CSP syntax
+  $("#csp_save").click( function() {
+    // if the advanced view is hidden, then generate policy from the visual
+    // editor and place in the form field
+    if ($("#csp_toggle_advanced").text() == "Show Advanced") {
+      $("#csp_value").val( generatePolicyFromSources(sources) );
+    }
+    // otherwise the user wants to edit the policy directly, so we'll use what's
+    // in the form field
+    $("form").submit();
+  });
+
+});
Index: wp-admin/js/jquery.url.js
===================================================================
--- wp-admin/js/jquery.url.js	(revision 0)
+++ wp-admin/js/jquery.url.js	(revision 0)
@@ -0,0 +1,214 @@
+/* ===========================================================================
+ *
+ * JQuery URL Parser
+ * Version 1.0
+ * Parses URLs and provides easy access to information within them.
+ *
+ * Author: Mark Perkins
+ * Author email: mark@allmarkedup.com
+ *
+ * For full documentation and more go to http://projects.allmarkedup.com/jquery_url_parser/
+ *
+ * ---------------------------------------------------------------------------
+ *
+ * CREDITS:
+ *
+ * Parser based on the Regex-based URI parser by Stephen Levithian.
+ * For more information (including a detailed explaination of the differences
+ * between the 'loose' and 'strict' pasing modes) visit http://blog.stevenlevithan.com/archives/parseuri
+ *
+ * ---------------------------------------------------------------------------
+ *
+ * LICENCE:
+ *
+ * Released under a MIT Licence. See licence.txt that should have been supplied with this file,
+ * or visit http://projects.allmarkedup.com/jquery_url_parser/licence.txt
+ *
+ * ---------------------------------------------------------------------------
+ * 
+ * EXAMPLES OF USE:
+ *
+ * Get the domain name (host) from the current page URL
+ * jQuery.url.attr("host")
+ *
+ * Get the query string value for 'item' for the current page
+ * jQuery.url.param("item") // null if it doesn't exist
+ *
+ * Get the second segment of the URI of the current page
+ * jQuery.url.segment(2) // null if it doesn't exist
+ *
+ * Get the protocol of a manually passed in URL
+ * jQuery.url.setUrl("http://allmarkedup.com/").attr("protocol") // returns 'http'
+ *
+ */
+
+jQuery.url = function()
+{
+	var segments = {};
+	
+	var parsed = {};
+	
+	/**
+    * Options object. Only the URI and strictMode values can be changed via the setters below.
+    */
+  	var options = {
+	
+		url : window.location, // default URI is the page in which the script is running
+		
+		strictMode: false, // 'loose' parsing by default
+	
+		key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], // keys available to query 
+		
+		q: {
+			name: "queryKey",
+			parser: /(?:^|&)([^&=]*)=?([^&]*)/g
+		},
+		
+		parser: {
+			strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, // more intuitive, fails on relative paths and deviates from specs
+			loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ //less intuitive, more accurate to the specs
+		}
+		
+	};
+	
+    /**
+     * Deals with the parsing of the URI according to the regex above.
+ 	 * Written by Steven Levithan - see credits at top.
+     */		
+	var parseUri = function()
+	{
+		str = decodeURI( options.url );
+		
+		var m = options.parser[ options.strictMode ? "strict" : "loose" ].exec( str );
+		var uri = {};
+		var i = 14;
+
+		while ( i-- ) {
+			uri[ options.key[i] ] = m[i] || "";
+		}
+
+		uri[ options.q.name ] = {};
+		uri[ options.key[12] ].replace( options.q.parser, function ( $0, $1, $2 ) {
+			if ($1) {
+				uri[options.q.name][$1] = $2;
+			}
+		});
+
+		return uri;
+	};
+
+    /**
+     * Returns the value of the passed in key from the parsed URI.
+  	 * 
+	 * @param string key The key whose value is required
+     */		
+	var key = function( key )
+	{
+		if ( ! parsed.length )
+		{
+			setUp(); // if the URI has not been parsed yet then do this first...	
+		} 
+		if ( key == "base" )
+		{
+			if ( parsed.port !== null && parsed.port !== "" )
+			{
+				return parsed.protocol+"://"+parsed.host+":"+parsed.port+"/";	
+			}
+			else
+			{
+				return parsed.protocol+"://"+parsed.host+"/";
+			}
+		}
+	
+		return ( parsed[key] === "" ) ? null : parsed[key];
+	};
+	
+	/**
+     * Returns the value of the required query string parameter.
+  	 * 
+	 * @param string item The parameter whose value is required
+     */		
+	var param = function( item )
+	{
+		if ( ! parsed.length )
+		{
+			setUp(); // if the URI has not been parsed yet then do this first...	
+		}
+		return ( parsed.queryKey[item] === null ) ? null : parsed.queryKey[item];
+	};
+
+    /**
+     * 'Constructor' (not really!) function.
+     *  Called whenever the URI changes to kick off re-parsing of the URI and splitting it up into segments. 
+     */	
+	var setUp = function()
+	{
+		parsed = parseUri();
+		
+		getSegments();	
+	};
+	
+    /**
+     * Splits up the body of the URI into segments (i.e. sections delimited by '/')
+     */
+	var getSegments = function()
+	{
+		var p = parsed.path;
+		segments = []; // clear out segments array
+		segments = parsed.path.length == 1 ? {} : ( p.charAt( p.length - 1 ) == "/" ? p.substring( 1, p.length - 1 ) : path = p.substring( 1 ) ).split("/");
+	};
+	
+	return {
+		
+	    /**
+	     * Sets the parsing mode - either strict or loose. Set to loose by default.
+	     *
+	     * @param string mode The mode to set the parser to. Anything apart from a value of 'strict' will set it to loose!
+	     */
+		setMode : function( mode )
+		{
+			strictMode = mode == "strict" ? true : false;
+			return this;
+		},
+		
+		/**
+	     * Sets URI to parse if you don't want to to parse the current page's URI.
+		 * Calling the function with no value for newUri resets it to the current page's URI.
+	     *
+	     * @param string newUri The URI to parse.
+	     */		
+		setUrl : function( newUri )
+		{
+			options.url = newUri === undefined ? window.location : newUri;
+			setUp();
+			return this;
+		},		
+		
+		/**
+	     * Returns the value of the specified URI segment. Segments are numbered from 1 to the number of segments.
+		 * For example the URI http://test.com/about/company/ segment(1) would return 'about'.
+		 *
+		 * If no integer is passed into the function it returns the number of segments in the URI.
+	     *
+	     * @param int pos The position of the segment to return. Can be empty.
+	     */	
+		segment : function( pos )
+		{
+			if ( ! parsed.length )
+			{
+				setUp(); // if the URI has not been parsed yet then do this first...	
+			} 
+			if ( pos === undefined )
+			{
+				return segments.length;
+			}
+			return ( segments[pos] === "" || segments[pos] === undefined ) ? null : segments[pos];
+		},
+		
+		attr : key, // provides public access to private 'key' function - see above
+		
+		param : param // provides public access to private 'param' function - see above
+		
+	};
+	
+}();
Index: wp-admin/options-security.php
===================================================================
--- wp-admin/options-security.php	(revision 0)
+++ wp-admin/options-security.php	(revision 0)
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Security Settings Administration Panel.
+ *
+ * @package WordPress
+ * @subpackage Administration
+ */
+
+/** Load WordPress Administration Bootstrap */
+require_once('./admin.php');
+
+if ( ! current_user_can('manage_options') )
+	wp_die(__('You do not have sufficient permissions to manage options for this blog.'));
+
+$title = __('Security Settings');
+$parent_file = 'options-general.php';
+wp_enqueue_style( 'theme-install' );
+wp_enqueue_style( 'security' );
+include('./admin-header.php');
+?>
+
+<div class="wrap">
+<?php screen_icon(); ?>
+<h2><?php echo esc_html( $title ); ?></h2>
+
+<form method="post" action="options.php">
+<?php settings_fields('security'); ?>
+
+<h3><?php _e('Content Security Policy'); ?></h3>
+<p><a href="https://wiki.mozilla.org/Security/CSP">Content Security Policy</a> is a mechanism that prevents <a href="http://en.wikipedia.org/wiki/Cross-site_scripting">cross-site scripting</a> and other content-injection attacks.  It works by informing the browser regarding which websites you trust to serve content in your pages, and what specific types of content are expected.  To provide protection in this way, CSP fundamentally changes the way JavaScript can be embedded in web pages, so be aware that <b>WordPress plugins may experience broken functionality</b> when CSP is enabled.</p>
+<table class="form-table" id="csp-form">
+<tr valign="top">
+<th><label for="csp_enabled"><?php _e('Enable CSP') ?></label></th>
+<td><input id="csp_enabled" type="checkbox" name="csp_enabled" value="1" <?php checked('1', get_option('csp_enabled')); ?> /></td>
+<th>Trusted Sites:</th>
+<td width="80%">
+  <div class="feature-filter" id="csp_display"></div>
+</td>
+<tr valign="middle">
+<td colspan="3"></td>
+<td><span id="csp_verify_text">Verify that you trust the sites in the list above to serve content in your site.  Remove any sites that you do not trust by clicking them.  Your changes will not be saved until you click <b>Save Changes</b> below.</span></td>
+</tr>
+</tr>
+<tr valign="top">
+<td colspan="3"></td>
+<td>
+  <div class="tablenav" style="vertical-align:bottom">
+    <div class="alignleft actions"><input type="button" name="suggestpolicy" id="suggestpolicy" class="button-secondary" value="<?php esc_attr_e('Suggest Policy') ?>" /><img id="content-loading" alt="" src="images/wpspin_light.gif"><input type="button" class="button-secondary" id="csp_discard" value="<?php esc_attr_e('Discard Changes') ?>" /><input type="button" class="button-primary" id="csp_save" value="<?php esc_attr_e('Save Changes') ?>" /></div>
+  </div>
+</td>
+</tr>
+<tr>
+<th><label id="csp_toggle_advanced">Show Advanced</label></th>
+<td colspan="3">
+  <div class="feature-filter" id="csp_advanced">
+    <p>Do not edit the policy directly unless you really know what you are doing.</p>
+    <input type="text" name="csp_value" id="csp_value" value="<?php form_option('csp_value'); ?>" />
+  </div>
+</td>
+</tr>
+<?php do_settings_fields('security', 'default'); ?>
+</table>
+
+<?php do_settings_sections('security'); ?>
+
+</form>
+
+</div>
+
+<iframe id="analyze"></iframe>
+<pre id="log"></pre>
+<script type="text/javascript" src="js/options-security.dev.js"></script>
+<script type="text/javascript" src="js/jquery.url.js"></script>
+<?php include('./admin-footer.php') ?>
Index: wp-admin/menu.php
===================================================================
--- wp-admin/menu.php	(revision 14075)
+++ wp-admin/menu.php	(working copy)
@@ -198,7 +198,8 @@ $menu[80] = array( __('Settings'), 'manage_options
 	$submenu['options-general.php'][25] = array(__('Discussion'), 'manage_options', 'options-discussion.php');
 	$submenu['options-general.php'][30] = array(__('Media'), 'manage_options', 'options-media.php');
 	$submenu['options-general.php'][35] = array(__('Privacy'), 'manage_options', 'options-privacy.php');
-	$submenu['options-general.php'][40] = array(__('Permalinks'), 'manage_options', 'options-permalink.php');
+	$submenu['options-general.php'][40] = array(__('Security'), 'manage_options', 'options-security.php');
+	$submenu['options-general.php'][45] = array(__('Permalinks'), 'manage_options', 'options-permalink.php');
 
 $_wp_last_utility_menu = 80; // The index of the last top-level menu in the utility menu group
 
Index: wp-admin/admin-header.php
===================================================================
--- wp-admin/admin-header.php	(revision 14075)
+++ wp-admin/admin-header.php	(working copy)
@@ -6,6 +6,10 @@
  * @subpackage Administration
  */
 
+// Content Security Policy header
+$csp = get_csp();
+if ($csp)
+  @header("X-Content-Security-Policy: " . $csp);
 @header('Content-Type: ' . get_option('html_type') . '; charset=' . get_option('blog_charset'));
 if (!isset($_GET["page"])) require_once('admin.php');
 
Index: wp-admin/images/everyone.gif
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: wp-admin/images/everyone.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Index: wp-admin/options.php
===================================================================
--- wp-admin/options.php	(revision 14075)
+++ wp-admin/options.php	(working copy)
@@ -38,6 +38,7 @@ $whitelist_options = array(
 	'discussion' => array( 'default_pingback_flag', 'default_ping_status', 'default_comment_status', 'comments_notify', 'moderation_notify', 'comment_moderation', 'require_name_email', 'comment_whitelist', 'comment_max_links', 'moderation_keys', 'blacklist_keys', 'show_avatars', 'avatar_rating', 'avatar_default', 'close_comments_for_old_posts', 'close_comments_days_old', 'thread_comments', 'thread_comments_depth', 'page_comments', 'comments_per_page', 'default_comments_page', 'comment_order', 'comment_registration' ),
 	'media' => array( 'thumbnail_size_w', 'thumbnail_size_h', 'thumbnail_crop', 'medium_size_w', 'medium_size_h', 'large_size_w', 'large_size_h', 'image_default_size', 'image_default_align', 'image_default_link_type', 'embed_autourls', 'embed_size_w', 'embed_size_h', 'uploads_use_yearmonth_folders', 'upload_path', 'upload_url_path' ),
 	'privacy' => array( 'blog_public' ),
+	'security' => array( 'csp_enabled', 'csp_value' ),
 	'reading' => array( 'posts_per_page', 'posts_per_rss', 'rss_use_excerpt', 'blog_charset', 'show_on_front', 'page_on_front', 'page_for_posts' ),
 	'writing' => array( 'default_post_edit_rows', 'use_smilies', 'default_category', 'default_email_category', 'use_balanceTags', 'default_link_category', 'enable_app', 'enable_xmlrpc' ),
 	'options' => array( '' ) );
Index: wp-admin/css/security.css
===================================================================
--- wp-admin/css/security.css	(revision 0)
+++ wp-admin/css/security.css	(revision 0)
@@ -0,0 +1,4 @@
+#suggestion, #analyze, #csp_discard, #csp_verify_text, #csp_advanced { display: none }
+img.csp-trusted { vertical-align:middle; margin: 0 1em }
+#content-loading { visibility:hidden; vertical-align:middle; margin:0 .5em }
+#csp_value { width: 75%; margin: 1em }
