Security Tip: Use the Alpine.js CSP Build!

[Tip#68] If you use Alpine and a CSP on your app, you'll want to use the new CSP-friendly build to avoid needing `unsafe-eval` in your policies.

Security Tip: Use the Alpine.js CSP Build!

👉 Looking to dive deeper into Laravel security? Check out Practical Laravel Security, my hands-on security course that uses interactive hacking challenges to teach you about how vulnerabilities work, so you can avoid them in your own code. 🕵️

👉 Worried about your app being hacked? Book in a Laravel Security Audit and Penetration Test! I can find the vulnerabilities before a hacker does, and help you fix them. 🕵️


We’ve talked about using Content Security Policies (CSP) before, but if you’re not familiar with them, then check out my Getting Started with CSPs tip and CSP In Depth articles to learn what they are and how they work. We haven’t talked about Alpine.js before though, so if you’ve never heard of it, go check out the website: alpinejs.dev. It’s a lightweight javascript framework with a inline-to-HTML syntax designed to avoid having to write complex external scripts.

Alpine is a great way to write minimal javascript, however it’s inline syntax uses Function declarations, which violate the `unsafe-eval` CSP directive. This meant you always had to choose between using Alpine or running a CSP without `unsafe-eval`, which was frustrating when you wanted to lock down your CSP but still use the cool features Alpine has to offer1.

As a result of many folks2 raising the issue, Caleb Porzio (the creator of Alpine and Livewire), recently announced a new CSP-friendly build of Alpine is available:

You can find all the details on the Alpine docs page: https://alpinejs.dev/advanced/csp.

The key difference with the CSP-friendly version is you can’t use inline scripts, but need to extract them into data components.

For example, you can’t do this with the CSP build:

<div x-data="{ count: 1 }">
    <button @click="count++">Increment</button>
 
    <span x-text="count"></span>
</div>

You need to do this instead:

<div x-data="counter">
    <button @click="increment">Increment</button>
 
    <span x-text="count"></span>
</div>
Alpine.data('counter', () => ({
    count: 1,
 
    increment() {
        this.count++
    },
}))

If you’re putting the work into running a strict CSP without `unsafe-eval`, then extracting components like this and being more intentional and structed around your javascript is a good trade-off, in my opinion.

Note that Livewire 3 doesn’t yet support the CSP build. Caleb is aware of the issue, and I’ll keep bugging him about it3 so we can make it happen.


Looking to learn more?
Security Tip #49: Disable Dev Tools on Prod
▶️ In Depth #17: Storing Environment Variables Safely

  1. I faced this problem with Practical Laravel Security. To avoid using Alpine, I wrote vanilla javascript for everything that needed JS, so I didn’t need to use `unsafe-eval` my CSP. Unfortunately, Livewire 3 ties Alpine directly into Livewire and so I had to enable `unsafe-eval`. CSP support for Livewire 3 isn’t available yet, but I’ll keep bugging Caleb about it! 😔

  2. Including myself!

  3. Or dive into it myself, if I get a chance.