[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.

Over the last few years, we have been performing audits on Posit
set-ups, Shiny Applications and general R set-ups. One of our standard
checks is to examine the server headers of a Shiny Server. Numerous
websites do this check for you, but as we have an R-based/Quarto
workflow, it was helpful to write a quick R

The package isn’t on CRAN, but is on the R-universe, so installing is

 repos = c("https://jumpingrivers.r-universe.dev",

There are only a couple of exported functions. The core function is
check(). As an example, let’s use

# check returns an invisible data frame of results
## ── Checking Server ──
## ✔ Status code: 301 → 301 → 200
## ✔ SSL available
## ✔ SSL redirection successful: http -> https
## ✔ content-security-policy: Policy present but not parsed
## ✔ content-type: charset set
## ✔ permissions-policy: Value present but not verified
## ✔ referrer-policy: Acceptable setting found
## ✔ strict-transport-security: max_age = 365 days and is greater than 1 year
## ✔ x-content-type-options: Acceptable setting found
## ✔ x-frame-options: Acceptable setting found

The output to the console highlights key server headers that we are
interested in. Of course, the definition of key is open to a lot of
discussion, but we just used
securityheaders.com for guidance.

Comments on jumpingrivers.com

Before we go further, it’s worth noting that a few years ago we decided
to move from WordPress to a static site generator – Hugo. We made this
decision based on

  • static sites are faster;
  • static sites are easier to maintain;
  • our previous site (WordPress) had to be constantly updated; dealing
    with numerous WordPress plugins always worried us – too much much for
    what is essentially a simple site.

One of the significant consequences of having a static site is the
attack surface is significantly reduced.

Status codes

The first header is the status code. You’re probably familiar with a
status code of 200 indicating a successful request, and the dreaded 404
indicating a missing page. However, when we look at
jumpingrivers.com, we actually got
three status codes: 301, 301, and then the magical 200. This is
fairly standard. What happens is that jumpingrivers.com is actually the
same as http://jumpingrivers.com. This redirects (code 301) to
https://jumpingrivers.com which redirects to

A “bad” site, wouldn’t redirect to the “https” version.

Content security policy

We’ve covered Content Security

(or CSP) in previous blog posts. By being explicit about where external
resources are loaded from, e.g. Javascript, it gives applications an
extra layer of security.

For example, we can state that Javascript can only be loaded from
jumpingrivers.com and example.com. Any JavaScript resource that is
loaded from another site is automatically blocked by the browser. This
safeguards against attacks such as cross-site scripting.

As jumpingrivers.com is a static site (we use Hugo), we don’t need to
worry about cross-site scripting quite as much; it’s probably overkill.
However, adding CSP to our site has highlighted exactly where we load
external resources from and has encouraged us to keep resources local
where possible.

Permissions policy

Permissions policy is similar to CSPs. Essentially, we specify the
resources we would load on our website. For example, would we expect to
use a camera or microphone? Again, for our static site this is overkill,
but for a Shiny application it’s certainly something you should

Want to ensure that your application or dashboard follows the latest standards? You might benefit from our Shiny health check.

Referrer policy

When someone clicks a link on a site that takes them to another domain,
the destination site receives information about where that user came
from. This is how we get website analytics about our site traffic.

This isn’t too important for a site like jumpingrivers.com as we don’t
have anything private on our site – everything is open to the world!
However, if your URL contains potentially private information that you
don’t want to be leaked, e.g. example.com/private-info then you should
set the Referrer Policy.

For jumpingrivers.com, we set it to no-referrer-when-downgrade. This
means when going from https to http, we won’t send the referrer header.
Other than that, we’ll send the full path.

Strict transport security

This header informs browsers that a site should only be accessed using
HTTPS. Once set, any future visits will automatically convert http to
https. Remember, from the status code, that typing jumpingrivers.com
into a browser, the URL automatically resolves to
http://jumpingrivers.com, so this (after the first visit) tightens up
this issue.

X content type options

This stops a browser from trying to MIME-sniff the content type. This
should be set to x-content-type-options: nosniff.

X frame options

This tells the browser whether or not you want to allow your site to be
framed. At jumpingrivers.com this is set to DENY.

Shiny servers

The {serverHeaders} package checks common security related headers.
There are certainly others, but the headers described above are
certainly the important one. Many Shiny applications we work with
contain sensitive data, help make business critical decisions and/or are
fundamental to a business process. As such, spending some time securing
your server is to be recommended (a little bit of understatement here).


This package is based
on a package originally created by Bob Rudis –

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.

R-bloggers.com 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.

Continue reading: Security Headers for Shiny Applications

Long-term Implications and Future Developments of Security Headers for Shiny Applications

In recent years, there has been a series of audits being performed on Posit set-ups, Shiny Applications, and general R set-ups. This analysis hinges on the examination of server headers of a Shiny Server, facilitated by an R-based/Quarto workflow. The importance of server headers in the context mentioned cannot be underestimated as they present numerous security benefits.

Potential Future Developments

Looking ahead, it’s likely that we’ll see several changes. The audit of server headers may become more necessary due to their effectiveness in securing information stored in servers. Further improvements are expected to be made on the R package used to examine these headers, contributing to a more efficient auditing process. This will trigger the need for future R packages to include more functionalities to better cater to diverse server configurations and security needs.

The Implications of the Security Headers

Security headers play an essential role in safeguarding servers from potential cyber-attacks. Headers manipulate how your content is rendered and can prevent various malicious attacks traditionally affecting web applications.

Well-secured static sites like Jumping Rivers have demonstrated that successfully audited servers can significantly reduce potential attack surfaces, leading to lower incidences of cybersecurity threats.


  1. For organizations using static sites, consider an audit on your Shiny Applications and general R set-ups. An audit can expose any vulnerabilities and offer solutions to reduce potential attack surfaces. Even if you consider your site to be low risk, such as Jumping Rivers, regular audits can provide insights into where external resources are loaded from, encouraging keeping resources local where possible.
  2. The implementation of Content Security Policies (CSPs) and Permission Policies is recommended. This approach is especially important for dynamic sites running Shiny applications, helping to ensure that your site blocks resources loaded from untrusted or unknown sources.
  3. Review your referrer policy. Any URL that may contain potentially private information should have referrer policies to prevent the leakage of sensitive data when users are directed to another domain.
  4. Ensure that the ‘strict transport security’ header is set on your website to maintain a secure connection by automatically converting http to https on all future website visits.
  5. To prevent MIME sniffing attacks, always set the ‘x content type options’ header to ‘nosniff’.
  6. For businesses dealing with sensitive data or making business-critical decisions on their platform, spending time securing your server is not an option but a necessity in today’s cyber world. Be proactive and consider running a Shiny health check to ensure your application dashboard follows the latest standards.

In conclusion, organizations and businesses should prioritize server audits and security headers implementation. Apart from safeguarding their platforms from possible cyber-attacks, these practices also position them as trustworthy entities among their clients.

Read the original article