Content Security Policy – Why You Need It

[This article was first published on The Jumping Rivers Blog, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Heads up! We’re about to launch WASP, a Web Application Security Platform. The aim of WASP is to help you manage (well, you guessed it) the security of your Posit Connect application using Content Security Policy and Network Error Logging. More details soon, but if this interests you, please get in touch.

This blog post is aimed at those who are somewhat tech literate but not necessarily a security expert. We’re aiming to introduce the concept of Content Security Policy and teach some of the technical aspects.

In 2018, a hacking group called Magecart exploited a vulnerability on the British Airways website that allowed them to inject JavaScript. The JavaScript code was used to send customer data to a malicious server, succeeding in skimming the credit cards of 380,000 transactions before the breach was discovered. This type of attack comes under the umbrella of cross-site scripting (XSS) – where malicious code (often client-side JavaScript) is injected into the browser.

What is Content Security Policy?

Content Security Policy (CSP) is a framework of modern (ish) browsers, that allows a developer to protect an application through the use of the Content-Security-Policy HTTP header. It’s used to give applications an extra layer of security – safeguarding against attacks such as cross-site scripting. In this blog we’re going to take you through some of the basics of Content Security Policy and show you why it’s a necessity for modern applications.

How will Content Security Policy help me?

In one way or another, you have made it to this blog post on This means your browser has already loaded a tonne of assets that this page needs to look and act in the way it does (JavaScript, fonts, stylesheets). Without CSP, the browser will trust and not question any loaded resources from any source. If there are any vulnerabilities with this page, an attacker could run client-side JavaScript to import content hosted from their own source; for instance, a fake form or a malicious click event to skim user details or steal data from a database, just like with British Airways. Your browser simply says “Yes, why wouldn’t I trust this code?”. This is where CSP comes into play.

Have you ever used the {shiny}, {quarto} or {rmarkdown} R packages to make web applications or documents? If you then took the extra step to deploy your app, you should be asking the question “How safe is it to deploy this?”. {shiny}, {quarto} and {rmarkdown} pull in a lot of external resources; css, JavaScript etc. This leaves them vulnerable to cross-site scripting attacks, just like British Airways. Using CSP, we can protect our {shiny} / {rmarkdown} documents against these attacks.

The technical basics

A Content Security Policy HTTP header is set on the server side, but protects the client side. A CSP header is split into directives – each directive enabling you to specify an allow list (in some cases, a deny list) of valid sources for content that the browser can (or is not allowed to) load. For instance, one of the more common directives, script-src, allows us to specify valid sources for scripts. Any scripts that are from a source not listed within this directive will be blocked from executing in the browser. A basic CSP header using script-src might be

Content-Security-Policy: script-src 'self'`

The metasource, self, is telling the browser to allow scripts to be loaded from our domain. As there are no others sourced listed with it, we are telling the browser to only allow scripts to be loaded from our domain. There are other metasources:

  • 'self': Content from the same domain,
  • 'none': Nobody can include this functionality. In the case above, this would mean we accept scripts from no sources.
  • A nonce / hash: Accept code with a specific nonce / hash.

Of course, we can also specify specific URL / domains. For instance,

Content-Security-Policy: script-src 'self'

would allow loading of scripts from our own domain, and Posit. Other common directives include

  • default-src: Default values for *-src directives.
  • font-src: Valid sources for fonts loaded using the @font-face CSS at-rule.
  • frame-src: Valid sources for embedded frame contents.
  • img-src: Valid origins from which images can be loaded.
  • navigate-to: Restricted URLs from which a document can initiate navigation.
  • style-src: Valid sources for stylesheets.
  • media-src: Valid sources for loading media using

For a full list, see the MDN Web Doc.

Reporting Content-Security-Policy violations

If an attacker had found any vulnerabilities on our site, then using the directives above we would be blocking a good bunch of potential attacks for users on modern browsers. However, users on browsers (mainly Internet Explorer) that still do not support the CSP directives you’ve chosen are still at threat. It’s important that we understand which CSP directives are being targeted on our site, to protect the vulnerable on old browsers.

Directives are split into two categories; blockers and reporters. Blockers block input into the application (think script-src) and reporters deliver reports about the blocks. This allows us to understand which of our CSP directives are being targeted.

The most important reporting directive is report-to. However, it’s predecessor, report-uri, still plays a crucial role. In fact, all browsers will fall back to report-uri if it can’t find report-to. We’ll go into more detail on the differences between the two in a later blog, but for now we’ll look into report-uri (it’s a tad simpler).

The report-uri directive allows us specify the URL(s) to which our CSP violation should be reported. These URLs are usually API endpoints, which process the report JSON. The following HTTP header would POST any violations to the csp-reporting endpoint on our domain

Content-Security-Policy: script-src 'self'; report-uri /csp-reporting

Any reports sent to this endpoint will be Content-Type: application/reports+json and contain four important pieces of information (plus some others):

  1. blocked-uri: URI of the blocked resource
  2. document-uri: URI of the document in which the violation occurred
  3. original-policy: The original Content Security Policy
  4. violated-directive: The CSP directive that was violated

The format will look something like

  "csp-report": {
    "document-uri": "",
    "referrer": "",
    "blocked-uri": "",
    "violated-directive": "script-src 'self'",
    "original-policy": script-src 'self'; report-uri /csp-reporting",
    "disposition": "report"

This report indicates that on the page, something has tried to load the style file located at However, because we have the script-src directive set to "self", only scripts from our own domain may be sourced.

Some limitations

Whilst CSP is a great addition to the security toolbox, there are some “limitations”:

  1. It’s not a magic wand. It’s a control to cut down on your application’s exposure – it will not patch vulnerabilities. Think of it like a firewall – it’s a secondary control, a defence technique. Mostly in case the developers have missed something. If you’re having trouble with any security issues, feel free to get in touch with us for advice.
  2. It’s only useful for client-side attacks on your application. It does not help with server-side, database attacks or anything in between.
  3. OK, this last one isn’t really a limitation, more of a warning. It’s not on by default. The Content-Security-Policy HTTP header has to be added manually with each policy individually specified.

If Content Security Policy or Shiny app security in general interests you or you want more news on WASP, our new Web Application Security Platform, then please email [email protected] and we can discuss how to set this up for your applications.

For updates and revisions to this article, see the original post

To leave a comment for the author, please follow the link and comment on their blog: The Jumping Rivers Blog. offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)