Skip to Content
Course content

164: Content Security Policy Basics

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing a PR for a teammate who had built a custom comment section for a community forum. He'd spent hours writing a "sanitization" function using regular expressions to strip out <script> tags from user input. He felt confident. Then, during a security audit, a pentester bypassed his filter using an <img> tag with an onerror attribute that executed JavaScript. Within seconds, the pentester had popped an alert box and demonstrated they could steal session cookies. The mistake wasn't just the regex—it was the assumption that we could catch every single way a browser might execute a malicious string. That's when I introduced him to Content Security Policy (CSP).

CSP is essentially a way for you to tell the browser, "I only trust these specific sources for my code." Instead of trying to scrub every single piece of user input (which is a losing battle), you set a policy that tells the browser to ignore any script it doesn't recognize, regardless of how it got onto the page. It's a powerful second line of defense that turns a catastrophic XSS vulnerability into a harmless console error.

Defining Your Trust Boundaries

You implement a CSP by sending a Content-Security-Policy HTTP header from your server, though you can also use a <meta> tag in your HTML for simpler setups. The policy consists of several "directives," each controlling a different type of resource. The most critical one is default-src, which acts as a fallback for everything else.

For example, if you set default-src 'self', you're telling the browser: "Only load resources (scripts, images, CSS) that originate from the exact same origin as this page." If a hacker manages to inject a script tag pointing to malicious-hacker-site.com, the browser will see that the domain isn't in your allowed list and will simply refuse to execute it. You'll see a loud, clear error in the DevTools console telling you exactly which policy was violated.

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://apis.google.com;">

In the example above, I've set a strict default, but I've specifically carved out an exception for Google's APIs. This is the standard workflow: lock everything down first, then surgically open the holes you actually need for your third-party libraries to work.

Dealing with the "Unsafe" Realities

Here is where things get tricky. By default, a strict CSP blocks inline scripts (code written directly inside <script> tags or HTML attributes like onclick) and the use of eval(). This is by design, because inline scripts are the primary vehicle for XSS attacks. However, many legacy projects or quick-and-dirty prototypes rely heavily on them.

If you absolutely must allow inline scripts, you can use the 'unsafe-inline' keyword. But I'll be honest: doing this basically disables the most important protection CSP provides. If you find yourself needing inline scripts but still want security, the professional move is to use a nonce (a "number used once"). You generate a random, cryptographically strong string on the server for every single request, add it to your CSP header, and then add the same string as an attribute to your script tag: <script nonce="EDNnf03nceIOfn39fn3e">...</script>. The browser only executes the script if the nonce matches the header.

It's a bit more overhead to implement, but it's the difference between a professional security posture and just hoping the hackers don't find your site today.




📋 Practical Task

Hardening a Vulnerable Portfolio Page against XSS

You have been handed a simple portfolio page that allows users to leave a "guestbook" message. The developer forgot to sanitize the input, and a user has successfully injected a script from an external domain: https://evil-scripts.net/steal.js. Your job is to implement a Content Security Policy via a <meta> tag to stop this attack.

Requirements:

  • Block all resources by default.
  • Allow scripts to be loaded ONLY from your own origin ('self') and from the trusted CDN https://cdn.jsdelivr.net.
  • Allow images to be loaded from any source (since the portfolio displays various external project images).
  • Ensure no inline scripts or eval() calls can execute.
<!DOCTYPE html>
<html>
<head>
    <title>My Portfolio</title>
    
</head>
<body>
    <h1>Welcome to my site!</h1>
    <div id="guestbook">
        
        <script src="https://evil-scripts.net/steal.js"></script>
        
        <script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
    </div>
    <img src="https://external-image-site.com/photo.jpg" alt="Project">
</body>
</html>
Rating
0 0

There are no comments for now.

to be the first to leave a comment.