Leo Content Security Policy (CSP)


The purpose of the content security policy manager is to allow the end-user administrator to limit external content that may be linked into a page. There are three main modes for this: OffReporting, and Enforcing. There are controls for allowing or disallowing content, iframes, links, and images from hosts other than the one serving Leo content. Depending on the level of enforcement configured in these settings content may be blocked from the same server depending on several factors in accordance with general origin determination rules. More to the point, by changing all possible settings to Enforcing it is possible to prevent the site from serving content at all unless rules are carefully designed and tested. 

Testing has been done at Leo with both working and broken rule sets. When a rule set is properly constructed there is only minimal impact to the speed at which Leo pages load, even during testing with rule sets that are purposely left incomplete. On the other hand, a poorly-constructed rule set can and will impact performance  if a page can successfully load at all. 


This web article explains several good rationales for using CSP. In part, it reads:

The primary benefit of CSP is preventing the exploitation of cross-site scripting vulnerabilities. When an application uses a strict policy, an attacker who finds an XSS bug will no longer be able to force the browser to execute malicious scripts on the page. The policy only allows scripts with the correct nonce value generated on the server, which attackers cannot guess, so they cannot inject their own scripts  subject to some caveats discussed in the FAQ. This is important because XSS bugs have two characteristics which make them a particularly serious threat to the security of web applications:

  • XSS is ubiquitous. Cross-site scripting has consistently been listed as one of the most common flaws in web applications; almost all large applications have suffered from XSS in the past. This one type of flaw consistently accounts for over 60% of payouts under Google's Vulnerability Reward Program.
  • XSS is damaging. An attacker who exploits such a bug and executes JavaScript in the context of another user's session gets full access to their data in the vulnerable application, and in all other applications hosted in the same domain. In many cases the attacker can compromise the account and retain persistent access, without the user realizing something is wrong.


Leo offers the CSP facility and this documentation on an "as-is", "bare implementation of the specification" as a tool for the sophisticated site administrator. Because each site's individual needs and OpSec requirements may vary widely, the onus for the proper implementation of a CSP rule set lies firmly with each site's administrator. This article supplies enough information where the neophyte can begin researching. There are links at the end of the article for explicit implementation information. What the rules might be for a specific site or installation are beyond the scope of either the software or this article. 

Definition of Terms

Within the scope of this article, these terms are defined as follows:

  • Off means that a setting is fully disabled and not used as an enforcement rule.
  • Reporting means that a setting is considered in use but will only report errors and objections to the Javascript console log in the browser. If report-uri is enabled then a copy of the log entries will be sent as JSON structures to the server. Obviously, use of this setting will have some impact on page loading and performance. The value "reporting" will not block objectionable or unsafe content; the browser will be allowed to continue.
  • Enforcing means that a setting is considered in use and a rule will prevent the covered content or content-type from being loaded or rendered. A console.log entry for each policy infraction will be made at the browser. If report-uri is enabled then a copy of the log entries will be sent as JSON structures to the server. Browser processing of the request ends immediately. The single quotes must be included.
  • 'none' refers to the empty set; that is, no URLs match. The single quotes must be included.
  • 'self' refers to the origin from which the protected document is being served, including the same URL scheme and port number. The single quotes must be included. Some browsers specifically exclude blob and filesystem from source directives. Sites needing to allow these content types can specify them using the Data attribute.
  • 'unsafe-inline' allows the use of inline resources, such as inline <script> elements, javascript: URLs, inline event handlers, and inline <style> elements. The single quotes must be included.
  • 'unsafe-eval' allows the use of eval() and similar methods for creating code from strings. The single quotes must be included.

Terminology includes but does not supersede IETF RFC 2119 (q.v.).


Step One:  Import Directives

Directives are the component of a CSP rule that indicate which kind(s) of content may be covered by the rule. These are defined by RFC and adopted for use by browser manufacturers. One browser may behave slightly differently than another for any one given directive, so take care in implementation and testing. The reference material below contains links to pages where tables of varying browser support and differences in implementation may be found.

The list of directives is static. Different levels of implementation do occasionally add or deprecate directives. Keep in mind that there will be some lag between the time when a list of accepted directives is published and when most browsers will adopt the implementation or deprecation of a directive.

Because the list is static and the names must be exact, Leo has a list of directives in the public repository from where these may be downloaded. If this is the first time that the new CSP system is to be deployed on a server instance, it will probably be necessary to navigate to module management and first run Update Schema to add the necessary tables. If the system is not at least at Leo version 4.407 then run Import Modules as well.

If the system software and schema are up to date then navigate to Home -> My Admin -> System Administrator -> CSP -> CSP Directive List. If the report shows that the system hasn't been previously loaded with the list of standard CSP directives, import them from the Leo public repository. If these directives are already in place, this step can be skipped. Directives rarely change and Leo should notify its customer base when a new revision is available. Otherwise, the directives table should not be changed once it is in place.

Step Two:  Refine Rule Sets Offline

Leo recommends the use of an external tool such as the User CSP plug-in for FireFox or CSP Tester for Chrome. With such a tool, rules can be defined and tested before implementation on the server without impacting site performance in any way. The only impact would be at a single browser, the one doing the testing and refinement.

Step Three:  Define the Implementation Rules

Before Starting

Before any implementation, administrator should review the reference material at the end of this article. It is very possible that an incorrect or overly-aggressive implementation with full reporting can bring an instance of Leo down. Optimally, rules should be tested on a test or QA instance before implementing them on a live system.

If Content Security Policy is a new concept for the site administrator, reviewing the material at Implementing Content Security Policy will be of particular benefit. Some explanations are quoted and expanded in the following paragraphs.

A well-crafted set of content security policy rules should have relatively little impact on either system performance or the rendering performance at the end-user's browser. The converse is also quite true: heavy use of reporting or less-well-crafted rules will have a demonstrable impact.

It is most important to remember that enforcement of these rules is at the viewer's browser and not in the web server. Even the best-written of rule sets can have an impact on the user experience due to decreased browser performance, however light. A poorly-written set of rules can potentially overwhelm the viewer's browser. Worse, such performance issues will be markedly exacerbated by the action of reporting violations back to the server if reporting mode is turned on. In that case, both end-user browsers and the school's Leo server can be completely overwhelmed. With reporting turned on, each and every access violation (of which there could be many) on each and every browser page across hundreds or thousands of connected users can potentially bring down the hardiest of servers. Test rules with care on non-live servers before porting rule sets to any live system.

The first thing that must be described is the default location from where content is served. That would be the server housing the instance of Leo. Rather than quoting the full URL where this server resides, it's enough to describe it as 'self' (with the single quotes -- these are required for this URI). For example, if this is the only location from which content will be allowed in a page, the resulting header would look like:

Content-Security-Policy: default-src 'self';

Think of building content security policies this way: unless and until any policy is in place, there is no filtering at the browser. With no stated rules in the HTTP header, any content is fair game. The instant the first rule is put into place, that builds a wall. What happens thereafter is that each succeeding rule set opens another tiny window in that wall to allow more content to be loaded in the browser. The rule in the example above builds a solid wall then opens a window through which only content from that one-and-only server will be loaded. All other content like graphics, fonts, cascading style sheets, etc from any other server will be blocked.

So what happens, for example, if page content is on the Leo server but course content contains images that may have been linked to from another site? The way the rule is set up above, those images will be blocked and won't appear in the content page where they would be expected.

There's a fix for that.

Add another rule in the CSP Rules page with a higher sequence number than the first rule for the cross-site images that may be shown. It's possible to use an asterisk (*) as a "match-all" wild card if the browser will be allowed access to multiple servers in another domain. For instance,

Content-Security-Policy: default-src 'self'; script-src 'self' *.partneruniversity.edu;

The 'default-src' rule, if used, should be stated first. In the CSP rule maintenance page, give this rule the lowest sequence number (1 or 10 for example, with all other rules having a higher sequence number, possibly starting at 100 or 1000). Even though this rule is stated first and one might think that succeeding rules simply cascade, this is not exactly the case. Caveats and exceptions are noted in the following paragraphs.

That simple sequence above allows the browser to load any content from this school's server and also scripts from any server in the "partner university" domain. That says that this school trusts only content from itself and one other school  and that school only for scripts. Images, cascading style sheets, media, and so forth must come from the originating server.

As rules are first stated then built up, it's very important to note that in this example the script-src has two parts and they must be stated that way. The reason is that once a directive is defined, it no longer inherits from 'default-src'. Each successive content type must specify 'self' as the primary source, otherwise the rule would indicate that content for that directive-type would come from somewhere other than the primary server.

It’s very important to always define default-src. Otherwise, the directives will default to allowing all resources. Because default-src 'self' was used in the directive above, it means that images served from the site’s domain will also be allowed.

default-src is a special directive that source directives will fall back to if they aren’t configured. However, the following directives don’t inherit from default-src, so be aware of this and remember that leaving these unset means they will either remain unset or may use the browser’s default settings:

  • base-uri
  • form-action
  • frame-ancestors
  • plugin-types
  • report-uri
  • sandbox

Setting 'self' as default-src is generally safe, because one controls one's own domain. However if it's necessary to default to locking things down more tightly then use default-src 'none' and explicitly list all known resource types. Given the example above, this would result in a policy that looks like:


   default-src 'none';

   img-src 'self';

   script-src 'self' https://code.jquery.com;

   style-src 'self';

Note from the Mozilla hacks site:

If you rely on prefetching, you might encounter problems with default-src 'none'. On AMO, we discovered that browser prefetching in Firefox will not be identified as a specific content type, therefore falling back to default-src. If default-src doesn’t cover the origin involved, the prefetched resource will be blocked. There’s a bug open with additional information on this issue.

The Kinds of Content That can be Controlled

The directives state what kind of content a rule will cover. The list of directives is well-known and published. There are tables at the links in the reference material section at the end of this article that show the current level of browser support for each.

A rule has two parts: the directive indicates what kind of content for which a rule is being specified; and the source from which the content is allowed to be accessed. The more rules there are in place, the more content the browser will be allowed to access and/or the more places from which the browser may access it. (This is a qualified assertion. See the caveats in the previous section for rules that don't inherit from default-src.)

Begin building rule sets by navigating to My Admin -> System Administration -> CSP -> CSP Rules. This maintenance suite is where the rules are defined against various directives. To add a new rule, click the New link at the top of the table listing.

  • Rule Name is a familiar name to identify why the rule exists and what it might cover. This has no effect on building the header. It exists solely as an identifier to the site administrator.
  • Directive  select the directive to be implemented from the drop-down list.
  • URI  the URL of the resource this rule is to cover. Wild cards may be used; for example, *.duke.edu would be any subdomain of duke.edu. A URI rule can be a full URL (server.dom.com), can contain wildcards (*.dom.com), or can also be one of the following:
    • 'none' refers to the empty set; that is, no URLs match. The single quotes must be included.
    • 'self' refers to the origin from which the protected document is being served, including the same URL scheme and port number. The single quotes must be included. Some browsers specifically exclude blob and filesystem from source directives. Sites needing to allow these content types can specify them using the Data attribute.
    • 'unsafe-inline' allows the use of inline resources, such as in-line <script> elements, javascript: URLs, inline event handlers, and inline <style> elements. The single quotes must be included.
    • 'unsafe-eval' allows the use of eval() and similar methods for creating code from strings.

NOTE: If any of these source indicators is used then the single quotes must be included.

The sequence number assists the header-builder to set URI rules in order under each directive. Rules are grouped together by directive, the sequence defining the order or the URIs for a given directive.

A typical starter rule set:

Rules may be deactivated during testing without wholly deleting them. This is to assist the system administrator in refining which rules and their implementation work best for that particular installation or Leo instance. As with most other data elements in the Leo system, "deleted" means only "marked as deleted". Thus, if a rule is marked deleted by mistake, run the report for deleted rules, select the rule in question, then mark as not deleted. The rule will be immediately active but such activation will have no effect until the header is regenerated.

Step Four:  Effect System-Wide Policy Changes

Navigate to My Admin -> System Administration -> Account Manager. Open the System Admin Options section by clicking the [+] icon in the tree view. Under [+] HTML Meta Tags, the system administrator may toggle CSP policy use on or off globally. Additionally, secure headers can be toggled in this tree view section.

NOTE: No change made in this menu has any effect until Step Five is accomplished.

Step Five:  Regenerate the cspMeta File (cspHeader.inc)

In order to effect the changes to content management by navigating to My Admin -> System Administration -> CSP. Select 'Regenerate Meta File' from the left-hand context menu. This will generate a file named cspHeader.inc that will be included automatically in each page generation thereafter.

Another file, cspMeta.inc is also generated. This provides commented copies of the actual HTTP header for CSP so the system administrator can see the effect of the generated rules. This file has no effect on page service or rule implementation and therefore can safely be deleted.

In case the activation of the rule set causes chaos due to one or more rules being in error the quickest remedy is to make an ssh terminal connection with the server then delete cspHeader.inc file from the service directory. That will stop CSP headers from being served, stopping the immediate pain, however the system administrator should immediately use the Account Manager screen in Leo to toggle CSP header service to OFF. Find and either delete or repair the rule that was in error, regenerate the meta file, then toggle CSP service back on to reactivate.

CSP Generate Meta FIle

CSP Generate Meta FIle

Meta File 'cspMeta.inc' Generated:

<!-- CSP Header Settings -->

<!-- @copyright Leo 2016 -->

<!-- Generated by Admin Admin -->

<!-- Created on 2016-10-14 14:10:59 -->

<!-- Directive 4 (default-src) + rule 1 URI ('self') -->

<!-- Directive 18 (script-src) + rule 2 URI ('self' 'unsafe-eval' 'unsafe-inline') -->

<!-- Directive 18 (script-src) + rule 4 URI (https://cloud.google.com/genomics/) -->

<!-- Directive 9 (img-src) + rule 5 URI ('self') -->

<!-- Directive 9 (img-src) + rule 6 URI (https://www.imaios.com/en/e-Anatomy/*) -->

<!-- meta http-equiv="Content-Type" content="text/html; charset=UTF-8" -->

<!-- meta http-equiv="X-XSS-Protection" content="1; mode=block" -->

<!-- meta http-equiv="X-XSS-X-Content-Type-Options" content="nosniff" -->

<!-- meta http-equiv="Strict-Transport-Security" content="max-age=31536000; includeSubDomains" -->

<!-- End of CSP Headers -->


Header File 'cspMeta.inc' Generated:

Content-Type: text/html; charset=UTF-8

X-XSS-Protection: 1; mode=block

X-XSS-X-Content-Type-Options: nosniff

Strict-Transport-Security: max-age=31536000; includeSubDomains

Content-Security-Policy-Report-Only: default-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://cloud.google.com/genomics/;img-src 'self' https://www.imaios.com/en/e-Anatomy/*; report-uri https://vm.dev.edu/dsmw/csp.php

Rule Sharing

The Leo system allows a facility for sharing of rules. Navigate to My Admin -> System Administration -> CSP -> CSP Rules. At the top of the lead report there are two links: XML for downloading and saving a rule set and Import for uploading a rule set. These facilities allow a campus with multiple instances of Lep (for instance, one for offline testing, another for quality assurance, and another for live use) to create and test rule sets then deploy them easily through the migration path. 

A rule set may be downloaded either to a repository on the server (typically /var/www/html/server-directory/data) or to the administrator's work station as an XML file. The XML file may be transferred up the migration chain for import into the resident Leo instance. If the file is saved to the server's data repository, the file name is always cspRule.xml and upload from the repository expects the same name. 

Conversely, the file name is immaterial when uploading or downloading to a local workstation. It must however be a correctly-formatted and tagged CSP rules XML file internally or the upload process will refuse to accept the file. 

Again, take great care if rules are exchanged between clients. Rules in the database can be overwritten or added to and unless they are carefully reviewed and tested before implementation, they can potentially impact application performance negatively. Such impact can even be severe enough to make a site unusable. 

Notes on Reporting Mode

When CSP rules are in place, generated into cspHeader.inc and turned on in the Account Manager, there will be performance impact at the browser. Usually this is minimal unless rules are poorly-tuned and the browser is logging a great number of rejections to the console log. 

Where performance impact will be most noticeable is when CSP Reporting Mode is turned on. This means that for every rejection that every connected browser encounters, a connection and call-back will be made with the Leo server to log the rejection. With potentially tens of rejections per page, multiplied by however many viewers are connected to Leo at a given moment, there could be tremendous degradation in available bandwidth and performance at the server as well. Each browser rejection due to a given rule is logged in a table named dcCSPViolations. 

Given these impacts, use the reporting mode sparingly to avoid occupying valuable server network bandwidth and to avoid occupying exorbitant disk space from out-of-control logging of violations. Leo recommends using reporting mode only for testing and even then only for a small, specific period of time sufficient to gather meaningful statistics or information necessary to tune and perfect CSP rule sets. \It is also highly recommended to purge the dcCSPViolations table using the CSP administrative function after testing is complete. 

Remember to turn reporting mode off (or disable CSP entirely) when testing or data-gathering is complete. 

Affected Content

By enabling any of the Content Security Policy switches the content types that may be affected include but are not limited to the following:

  • Invocations of the XMLHttpRequest API in a cross-site manner.
  • Web fonts (for cross-domain font usage in @font-face within CSS), so that servers can deploy TrueType fonts that can only be cross-site loaded and used by web sites that are permitted to do so.
  • WebGL textures.
  • Images/video frames drawn to a canvas using drawImage.
  • Style sheets (for CSSOM access).
  • Scripts (for unmuted exceptions).

Reference Material

CSP Policy Directives 

This is a list of content policy directives, what they are for, and which portions of content each affects. Leo strongly recommends starting here in order to understand which directives are supported in the framework and what the outcome may be if such settings are changed. 

Content Security Policy Reference 

The new Content-Security-Policy HTTP response header helps you reduce XSS risks on modern browsers by declaring what dynamic resources are allowed to load via a HTTP Header. 

CSP 1.0 Specification 

CSP Level 2.0 Specification 

Mozilla Tutorial: Implementing Content Security Policy 

WikiPedia: Same-Origin Policies 


In computing, the same-origin policy is an important concept in the web application security model. Under the policy, a web browser permits scripts contained in a first web page to access data in a second web page, but only if both web pages have the same origin. An origin is defined as a combination of URI scheme, hostname, and port number. This policy prevents a malicious script on one page from obtaining access to sensitive data on another web page through that page's Document Object Model. This mechanism bears a particular significance for modern web applications that extensively depend on HTTP cookies to maintain authenticated user sessions, as servers act based on the HTTP cookie information to reveal sensitive information or take state-changing actions. A strict separation between content provided by unrelated sites must be maintained on the client-side to prevent the loss of data confidentiality or integrity. 

HTTP Access Control (CORS) 


A resource makes a cross-origin HTTP request when it requests a resource from a different domain than the one which the first resource itself serves. For example, an HTML page served from http://domain-a.com makes an <img> src request for http://domain-b.com/image.jpg. Many pages on the web today load resources like CSS stylesheets, images and scripts from separate domains. For security reasons, browsers restrict cross-origin HTTP requests initiated from within scripts. For example, XMLHttpRequest follows the same-origin policy. So, a web application using XMLHttpRequest could only make HTTP requests to its own domain. To improve web applications, developers asked browser vendors to allow XMLHttpRequest to make cross-domain requests. The W3C Web Applications Working Group recommends the new Cross-Origin Resource Sharing (CORS) mechanism. CORS gives web servers cross-domain access controls, which enable secure cross-domain data transfers. Modern browsers use CORS in an API container - such as XMLHttpRequest - to mitigate risks of cross-origin HTTP requests. 

OWASP: Content Security Policy Cheat Sheet 


Content Security Policy (CSP) is an effective "defense in depth" technique to be used against content injection attacks. It is a declarative policy that informs the user agent what are valid sources to load from. Since, it was introduced in Firefox version 4 by Mozilla, it has been adopted as a standard, and grown in adoption and capabilities. 



Note: Setting the meta tag is useless. For instance, <meta http-equiv="X-Frame-Options" content="deny"> has no effect. Do not use it! Only by setting through the HTTP header like the examples [in the same document], X-Frame-Options will work. 

If this header is to be respected by browsers, it must be set with the PHP header() function. 

HTTP Header Syntax 

List of HTTP Header Fields