[{"data":1,"prerenderedAt":183},["ShallowReactive",2],{"work-liquidguard-sast":3},{"id":4,"title":5,"body":6,"category":143,"challenge":144,"codeSnippet":145,"description":149,"extension":150,"featured":151,"finalScreenshot":152,"gridOrder":153,"isDemo":151,"isDemoURL":136,"liveUrl":153,"meta":154,"mockupImage":155,"navigation":156,"nextProject":157,"order":160,"path":161,"results":162,"schneiderFeatured":151,"schneiderOrder":153,"seo":163,"solution":164,"stem":165,"subtitle":153,"tags":166,"techStack":172,"thumbnail":180,"thumbnailVideo":153,"year":181,"__hash__":182},"works/works/liquidguard-sast.md","LiquidGuard SAST",{"type":7,"value":8,"toc":135},"minimark",[9,14,23,26,30,45,68,85,91,95,101,118,124,128],[10,11,13],"h2",{"id":12},"the-problem","The Problem",[15,16,17,18,22],"p",{},"Shopify themes are the front door to merchant revenue, yet there's no dedicated security tooling for Liquid templates. Developers routinely output customer data, metafield values, and form inputs without proper escaping — not out of negligence, but because Liquid's security model is subtle. The ",[19,20,21],"code",{},"| escape"," filter handles HTML context, but what about JavaScript string contexts? URL attributes? CSS injection vectors?",[15,24,25],{},"Generic SAST tools treat Liquid as plain text and miss the entire template language layer. Theme review processes rely on manual code review, which doesn't scale and misses non-obvious data flow paths.",[10,27,29],{"id":28},"architecture","Architecture",[15,31,32,36,37,40,41,44],{},[33,34,35],"strong",{},"Liquid Parser"," — A custom parser built on Tree-sitter grammar definitions that produces a full AST from Liquid templates. Unlike regex-based approaches, this correctly handles nested tags, complex filter chains, and multi-file template inheritance (",[19,38,39],{},"{% render %}",", ",[19,42,43],{},"{% section %}",").",[15,46,47,50,51,40,54,40,57,40,60,63,64,67],{},[33,48,49],{},"Taint Analysis"," — The core analysis engine traces data from sources to sinks. Sources include ",[19,52,53],{},"customer.*",[19,55,56],{},"form.*",[19,58,59],{},"comment.*",[19,61,62],{},"product.metafields.*",", and any variable populated from URL parameters. Sinks are output statements (",[19,65,66],{},"{{ }}",") and certain tag attributes.",[15,69,70,73,74,76,77,80,81,84],{},[33,71,72],{},"Context-Aware Rules"," — The scanner understands that different output contexts require different sanitization. An ",[19,75,21],{}," filter is sufficient for HTML body context but insufficient for ",[19,78,79],{},"href"," attributes (needs URL validation) or ",[19,82,83],{},"\u003Cscript>"," blocks (needs JSON encoding).",[15,86,87,90],{},[33,88,89],{},"CLI & CI Integration"," — Ships as a zero-config CLI tool. Point it at a theme directory, get a report. Integrates into GitHub Actions with SARIF output for code scanning alerts.",[10,92,94],{"id":93},"key-technical-decisions","Key Technical Decisions",[15,96,97,100],{},[33,98,99],{},"Why Tree-sitter over hand-written parser?"," Tree-sitter provides error recovery (partial parse of malformed templates), incremental parsing (fast re-analysis on file changes), and a battle-tested parsing foundation. The Liquid grammar definition is the custom piece, not the parser infrastructure.",[15,102,103,106,107,110,111,114,115,117],{},[33,104,105],{},"Why taint tracking over pattern matching?"," Pattern matching catches ",[19,108,109],{},"{{ customer.email }}"," but misses ",[19,112,113],{},"{% assign x = customer.email %}{{ x }}",". Taint analysis follows the data through assignments, captures, and even across ",[19,116,39],{}," boundaries with parameter passing.",[15,119,120,123],{},[33,121,122],{},"Why zero-config?"," Security tools that require configuration don't get used. The scanner infers the Shopify context automatically (theme vs. section vs. snippet) and applies appropriate rules.",[10,125,127],{"id":126},"outcome","Outcome",[15,129,130,131,134],{},"LiquidGuard has scanned 50+ production Shopify themes and found security issues in every single one. The most common finding is unescaped output in JavaScript contexts (",[19,132,133],{},"\u003Cscript>var x = \"{{ value }}\";\u003C/script>","), which standard Liquid escaping doesn't protect against. The tool now runs in CI for several Shopify Plus agencies.",{"title":136,"searchDepth":137,"depth":137,"links":138},"",2,[139,140,141,142],{"id":12,"depth":137,"text":13},{"id":28,"depth":137,"text":29},{"id":93,"depth":137,"text":94},{"id":126,"depth":137,"text":127},"Security Tool","Shopify themes handle customer data, payment information, and user-generated content — but there's no security scanning tool purpose-built for Liquid. Generic SAST tools don't understand Liquid's template syntax, filter chains, or Shopify-specific security contexts.",{"language":146,"filename":147,"code":148},"typescript","src/analyzers/xss-detector.ts","export function analyzeOutputStatement(\n  node: LiquidOutputNode,\n  scope: AnalysisScope\n): SecurityFinding[] {\n  const findings: SecurityFinding[] = []\n  const outputContext = determineContext(node)\n  const dataSource = resolveDataSource(node.expression, scope)\n\n  if (!dataSource.isTrusted) {\n    const filterChain = extractFilters(node)\n    const sanitized = filterChain.some(f =>\n      SANITIZING_FILTERS[outputContext]?.includes(f.name)\n    )\n\n    if (!sanitized) {\n      findings.push({\n        severity: 'high',\n        rule: 'xss-unescaped-output',\n        message: `Untrusted data \"${dataSource.name}\" rendered in ` +\n          `${outputContext} context without sanitization`,\n        location: node.location,\n        fix: suggestFilter(outputContext),\n      })\n    }\n  }\n\n  return findings\n}\n","A static analysis security scanner for Shopify Liquid templates that detects XSS vulnerabilities, unsafe data flows, and insecure patterns at the AST level before they reach production.","md",false,"/images/projects/liquidguard-sast-final.jpg",null,{},"/images/projects/liquidguard-sast-mockup.jpg",true,{"title":158,"slug":159},"Speed Score","shopify-perf-audit",3,"/works/liquidguard-sast","Detected an average of 12 security issues per theme in audits of 50+ Shopify themes. Zero false positive rate on XSS detection in HTML context. CLI runs full theme scan in under 3 seconds.",{"title":5,"description":149},"Built a custom Liquid parser that generates an Abstract Syntax Tree, then walks the tree to trace data flows from untrusted sources (customer input, metafields) through filter chains to output contexts (HTML, JavaScript, URL attributes), flagging unescaped or improperly sanitized paths.","works/liquidguard-sast",[167,168,169,170,171],"Security","AST","Liquid","Static Analysis","CLI",[173,174,175,176,177,178,179],"TypeScript","Tree-sitter","Liquid AST","Node.js","CLI (Commander)","Vitest","GitHub Actions","/images/projects/liquidguard-sast.jpg","2026","v2VBGGO4up1QQxid5qDVTSU0IYGclino5SQeyYr_dcU",1773700381450]