A Content Security Policy (CSP) is a strategy that restricts how much access any request has to an application's content. It also controls the inbound content from input forms and browser navigation bars. On a grand scale, a good CSP will protect your application from injection-based attacks such as cross-site scripting (XSS.)
This post is a good launchpad for CSP implementations in your Rust applications. Rust is famed for memory safety, along with other attributes. This makes it easy for developers to assume security-level safety when deploying applications with Rust in the background. We'll go in depth on the best ways to implement a Content Security Policy in Rust applications.
Let's start with contextualizing a Content Security Policy to the Rust programming language.
What Is a Rust Content Security Policy?
When deploying web applications into production, you can direct the browsers and user devices that'll access the apps on how best your content loads. For content security, we use HTTP headers. When it comes to a Rust-built (back-end) application, we set these headers on the front end—often with specific directives that act as the first line of defense against malicious use of your application by hackers.
After implementation (on the front end), these headers follow the standard syntax:
Content-Security-Policy: <first policy-directive>; <second policy-directive>;...; <nth policy-directive>
However, you have the option to declare and set each policy with Rust code on the back end. This way, you get to call them as a function, and if you need to make changes, you'll put them in effect once and they'll apply throughout your application.
Here's how such an implementation looks when written on the back end in Rust-lang:
extern crate content_security_policy; use content_security_policy::*; fn main() { let csp_list = CspList::parse("script-src *.notriddle.com", PolicySource::Header, PolicyDisposition::Enforce); let (check_result, _) = csp_list.should_request_be_blocked(&Request { url: Url::parse("https://www.easy.com/script.js").unwrap(), origin: Origin::Tuple("https".to_string(), url::Host::Domain("easy.com".to_owned()), 443), redirect_count: 0, destination: Destination::Script, initiator: Initiator::None, nonce: String::new(), integrity_metadata: String::new(), parser_metadata: ParserMetadata::None, });
The snippet above declares a variable csp_list, in which it packs all the directives for the application's interaction with access platforms. When set, CSP directives often fire up errors in the console to help with debugging and scaling features in your applications. Let's look at some of these errors and decipher them.
Example CSP Errors
For this section, we've pulled CSP error content from forums and development communities to cover more ground than we'd be able to simulate. Let's get right into it.
Source: Content Security Policy, Google Developers Community
The error highlighted in bright red reads as follows:
Refused to load script 'http://evil.com/evil.js' because it violates the following Content Security directive: "script-src 'self' https:apis.google.com".
This error message flags in the console because the specific application under inspection will have specified the script-src 'self' directive. This specific directive stops a web application from loading an external source of content. Here, it's 'http://evil.com/evil.js'.
Every error is specific to the directive enforcing content security, as declared in the CSP headers. Consider the error below extracted from the Mozilla Foundation platform via console:
This report shows a single CSP error caused by the font-src directive. This error persists unless the intended source is allowlisted under the CSP header declaration.
CSP Directives You Should Know
Debugging errors is all about knowing the limitations imposed by specific CSP directives. Source, or script, allowlisting only makes sense when you're absolutely sure that your leniency won't expose your users to threats in the aftermath. That said, let's look at some directives and how they affect your Rust application's access behavior.
At this point, you've already come across the font-src and script-src directives, both of which fall into the fetch-directive category. As hinted by the fetch keyword, these two directives and more in their category determine the source of scripts and content from which your Rust application can render. You can read the -src part of a directive and know that it is of type fetch.
Other fetch directives include the following:
connect-src. This is a directive that limits the source of connection to trusted URLs. This way, you can't have your Rust application loading content (or redirecting) from a different destination than itself, as is the case when hackers launch cross-site scripting attacks.
image-src. This directive limits the images loaded on your web application to known sources. Often this is one source: the application storage location.
frame-src. With frames capable of loading external content into your application (iframes), this directive averts the possibility of nesting malicious content into your own. Limiting the trusted sources range makes iframe source alterations a futile-cross scripting effort.
The same specific limitations count with the rest of the fetch directives: media-src, style-src, script-src.
Other directive types include document, navigation, and reporting policies. Their purposes are to manage the environment, how users interact with the application, and the actual error reporting process itself, respectively.
How to Implement CSP in Rust
As you may have gathered by now, each CSP directive forms an invisible fence around the application. Specific to Rust applications, you have two CSP implementation options:
You can have the headers bare and declared on the front end. These appear as HTML, revealing to anyone clever enough to inspect your document source just what you have enforced and what you allow.
Or you can use CSP crates and raw code on the back end of your Rust application. This route makes more sense from security and usability standpoints as your directives remain a secret to any hackers.
We'll discuss the back-end Rust CSP implementation method that takes the least effort, and yet covers as many directives as you need: applying CSP crates to your application.
Rust CSP Crates
Crates are a good way of adding features to your Rust application without having to write lengthy blocks of code on your own—avoiding the reinventing of the wheel scenario. Even when you need to apply custom variables, using crates is a good starting point when building robust applications. However, be sure to test all crates before you patch your code with theirs.
With the above in mind, consider the implementation example shown below. The aim is to add CSP headers using the Rust Ammonia package/crate.
The first step in the process is to add the crate as a dependency in the cargo.toml file of your Rust project.
[dependencies]
content-security-policy = "0.4.2"
Let's review the content-security-policy implementation instance below. As you read, pick out the directives and their states as you build out the resulting headers.
extern crate content_security_policy;
use content_security_policy::*;
fn main() {
let csp_list = CspList::parse("script-src *.notriddle.com", PolicySource::Header, PolicyDisposition::Enforce);
let (check_result, _) = csp_list.should_request_be_blocked(&Request {
url: Url::parse("https://www.notriddle.com/script.js").unwrap(),
origin: Origin::Tuple("https".to_string(), url::Host::Domain("notriddle.com".to_owned()), 443),
redirect_count: 0,
destination: Destination::Script,
initiator: Initiator::None,
nonce: String::new(),
integrity_metadata: String::new(),
parser_metadata: ParserMetadata::None,
});
assert_eq!(check_result, CheckResult::Allowed);
let (check_result, _) = csp_list.should_request_be_blocked(&Request {
url: Url::parse("https://www.evil.example/script.js").unwrap(),
origin: Origin::Tuple("https".to_string(), url::Host::Domain("notriddle.com".to_owned()), 443),
redirect_count: 0,
destination: Destination::Script,
initiator: Initiator::None,
nonce: String::new(),
integrity_metadata: String::new(),
parser_metadata: ParserMetadata::None,
});
assert_eq!(check_result, CheckResult::Blocked);
}
Declaring your Rust CSP headers this way and tucking the files in the back end is a safety-first coding practice. By no means does this mean you can relax thereafter. CSP is not supposed to be the only header in your security arsenal. Rather, it forms part of an elaborate security scheme that involves other headers like the HSTS to compound your Rust application's security.
Conclusion: Enforcing Rust Content Security Policy
As you add to your security-first coding practices, check any new feature additions for compliance with your CSP directives. You could do this manually with every code review and testing iteration. However, you risk losing time and even some vulnerabilities escaping into production. Using StackHawk tools makes it certain that no security flaws escape into your live environment with time. This way, you can assure the best user experience as your user base grows (which includes hackers too).
This post was written by Taurai Mutimutema. Taurai is a systems analyst with a knack for writing, which was probably sparked by the need to document technical processes during code and implementation sessions. He enjoys learning new technology and talks about tech even more than he writes.