Dangerous keys (__proto__, constructor, prototype) are blocked only when
ownPropertyOnly is true (default). With false, full prototype access is
allowed as an explicit opt-out; use bourne for untrusted input.
Co-authored-by: Cursor <[email protected]>
Proto keys were incorrectly blocked even when ownPropertyOnly=false.
Inherited access is now gated only by ownPropertyOnly; docs updated.
Co-authored-by: Cursor <[email protected]>
Proto keys block inherited access only; ownPropertyOnly is checked once before return for all keys. Own __proto__/constructor/prototype properties are readable—sanitize untrusted scope input.
Co-authored-by: Cursor <[email protected]>
Split the OR condition into two explicit checks so inherited proto keys are always blocked and own proto keys are blocked only when ownPropertyOnly is true.
Co-authored-by: Cursor <[email protected]>
Set.has already returns false for non-string PropertyKey values; widen
BLOCKED_SCOPE_KEYS type so TypeScript accepts the direct has(key) call.
Co-authored-by: Cursor <[email protected]>
Inline findScope and blocked-key checks, shorten ownPropertyOnly docs,
and drop implementation-detail push() unit tests.
Co-authored-by: Cursor <[email protected]>
Drop null-prototype passthrough in push(), inline blocked-key checks,
remove redundant createScope at include tag, trim verbose docs, and
drop implementation-detail unit tests.
Co-authored-by: Cursor <[email protected]>
Restore null-prototype hardening for Jekyll include bindings, colocate blocked-key checks with readJSProperty, align ownPropertyOnly JSDoc with security docs, and drop integration tests duplicated in context.spec.
Co-authored-by: Cursor <[email protected]>
Centralize null-prototype scope creation in push() so callers pass plain objects; Drop instances and existing null-proto frames are pushed as-is. Remove sanitizeScope in favor of createScope via Object.assign.
Move readSize, readFirst, and readLast to private Context methods using this.ownPropertyOnly. Remove redundant shouldBlockScopeKeyRead from findScope.
Co-authored-by: Cursor <[email protected]>
Use shouldBlockScopeKeyRead in findScope hasKey so inherited
constructor/__proto__/prototype do not falsely match environments.
Remove redundant globals hasKey check; globals remains the fallback scope.
Co-authored-by: Cursor <[email protected]>
Iteration is documented as an ownPropertyOnly exception; restore
isIterable/toEnumerable and document inherited Symbol.iterator behavior.
Co-authored-by: Cursor <[email protected]>
Block __proto__, constructor, and prototype only when ownPropertyOnly
is true or when access would traverse the prototype chain. Allow own
properties with those names when ownPropertyOnly is false.
Co-authored-by: Cursor <[email protected]>
Block writes to dangerous keys in assign/capture/increment/decrement, use own-property Symbol.iterator for plain objects when ownPropertyOnly is true, fix inherited size reads, and sanitize filter iteration scopes.
Co-authored-by: Cursor <[email protected]>
* feat: remove memoryLimit option (#910)
Co-authored-by: Cursor <[email protected]>
* feat: add templateLimit, outputLengthLimit, and maxDepth DoS limits
Enforce v11 resource guards in render and tags, fix for offset/else behavior, and update tutorials for Tag-class registration.
Co-authored-by: Cursor <[email protected]>
* docs: revert unnecessary tutorial churn from memoryLimit PR
Restore the two-example register-filters-tags structure (Value + Hash)
and undo unrelated constructor/emitter doc edits not required for DoS limits.
Co-authored-by: Cursor <[email protected]>
* docs: trim security-model prose and update render-tag-content
Remove diary-style engine comparisons from security-model.md.
Update render-tag-content tutorial to Tag class examples with tpls class field.
Co-authored-by: Cursor <[email protected]>
* docs: note maxDepth stack overflow applies to renderSync only
Explain why async render does not need maxDepth for stack protection based on generator/toPromise driving.
Co-authored-by: Cursor <[email protected]>
* refactor: track maxDepth via depthLimit Limiter on Context
Replace increaseDepth/decreaseDepth with a shared Limiter that supports
paired use/release, matching templateLimit and outputLengthLimit patterns.
Co-authored-by: Cursor <[email protected]>
* fix: remove spurious diff noise in filter files
Restore misc.ts from origin/next with LF line endings and re-apply only
memoryLimit removal, avoiding CRLF and blank-line churn in the export block.
Co-authored-by: Cursor <[email protected]>
* refactor: minimize PR diff noise
Co-authored-by: Cursor <[email protected]>
* feat: cap strftime pad width at 1M
docs: restructure security model with production guidance
Co-authored-by: Cursor <[email protected]>
* refactor: simplify depthLimit in partial tags and tighten security docs
Drop try/finally around depthLimit in include, layout, and render; release at generator end. Consolidate production guidance in security-model.md. Fix padded-blocks lint in dos.spec.ts.
Co-authored-by: Cursor <[email protected]>
---------
Co-authored-by: Cursor <[email protected]>
Browser UMD/min bundles already compile with ES2020 on next; drop the
ES5-only downlevelIteration override from rollup.config.mjs.
Co-authored-by: Cursor <[email protected]>
Remove KeepingTypeEmitter and always stringify via SimpleEmitter. On next, map breaking commits to patch so alpha stays on 11.x.
Co-authored-by: Cursor <[email protected]>
* feat!: drop TagImplOptions in favor of Tag classes (#839)
Remove tag-options-adapter and the registerTag object-literal overload.
Custom tags must extend Tag.
Co-authored-by: Cursor <[email protected]>
* test: drop TagImplOptions-specific e2e coverage (#839)
Remove #570 v9 object-literal registration test and unused metadata_file setup in #573.
Co-authored-by: Cursor <[email protected]>
* test: use inline Tag classes in register-tags spec
Co-authored-by: Cursor <[email protected]>
* test: remove dead throwingTag setup and duplicate throw stub
for.spec kept throwingTag registration after #713 removed its test. Reuse ThrowingTag in liquid.spec instead of IntendedRenderErrorTag.
Co-authored-by: Cursor <[email protected]>
* test: remove dead throwingTag setup and duplicate throw stub
for.spec kept throwingTag registration after #713 removed its test. Reuse ThrowingTag in liquid.spec instead of IntendedRenderErrorTag.
Co-authored-by: Cursor <[email protected]>
* fix(demo): ignore killall exit when express server already stopped
Co-authored-by: Cursor <[email protected]>
* fix(demo): revert unrelated return->exit change in express test
Co-authored-by: Cursor <[email protected]>
* fix(demo): use exit in express test script (no enclosing function)
Co-authored-by: Cursor <[email protected]>
---------
Co-authored-by: Cursor <[email protected]>
* fix(filters): charge join/array_to_sentence_string by output size
join charged memoryLimit by array element count, not by the string it
produces, letting concat doubling (cheap reference copies) inflate an
array's element count and then materialize a huge string via join far
past the configured memoryLimit (GHSA-4r6h-5v86-94p3). Charge by the
sum of stringified element lengths plus separators before allocating.
Apply the same fix to the sibling array_to_sentence_string filter.
Co-authored-by: Cursor <[email protected]>
* refactor(filters): simplify join output-size accounting
Sum stringified element lengths in a single pass and keep the guarded
Array.prototype.join for the result, instead of building an intermediate
parts array.
Co-authored-by: Cursor <[email protected]>
* fix(filters): charge json/jsonify/inspect serialization to memoryLimit
json/jsonify/inspect serialized values without charging memoryLimit, so
a concat-doubled array (cheap reference copies) could be materialized
into a huge JSON string past the configured limit — the same unbounded
class as the join bug (GHSA-4r6h-5v86-94p3). Charge via a JSON.stringify
replacer that accounts string lengths as it walks, aborting mid-
serialization instead of allocating the full blob first.
Co-authored-by: Cursor <[email protected]>
* fix(memory): charge rendered output to memoryLimit at emission
Move output-length accounting into the emitters, which charge each
written chunk against ctx.memoryLimit right before it reaches the
result string or stream. Filters/tags now only pre-charge the extra
working memory they allocate apart from that output, so join drops its
bespoke output-size counting and charges array.length like its siblings.
The block.super capture emitter intentionally omits the limiter to
avoid double-counting content that is re-emitted through the final
emitter.
Co-authored-by: Cursor <[email protected]>
* refactor(filters): rely on emitter output charge for json/inspect/array_to_sentence_string
With rendered output charged at emission, these filters no longer need
bespoke output-size counting: the emitted case is covered by the final
emitter. Revert json/inspect to their original form and array_to_sentence_string
to its element-count charge, dropping the non-emitted `| size` guards.
Co-authored-by: Cursor <[email protected]>
* revert(memory): drop emitter output charge, restore filter output-size accounting
join/array_to_sentence_string/json/inspect charge memoryLimit by the
string they materialize (not element count), so discarded results like
{% assign out = a | join %}{{ out | size }} are still bounded.
Remove the emitter-level limiter added in 2f343f063; it cannot catch
materialized-but-not-emitted values.
Co-authored-by: Cursor <[email protected]>
* fix(filters): charge json/inspect replacer by serialized node size
Replace the flat 1-unit charge for non-string JSON nodes with per-type
estimates (primitives via JSON.stringify length, containers by structure).
Co-authored-by: Cursor <[email protected]>
---------
Co-authored-by: Cursor <[email protected]>
* fix: enforce ownPropertyOnly for inherited array indices
Route array index access (including negative indices, first/last, and the
first/last filters) through a shared readArrayElement helper so that
ownPropertyOnly hides prototype-inherited array indices, closing the
GHSA-fwxr-j5w2-587m bypass. The option's scope (property/index access
only, not filter transforms or iteration) is documented on the option.
Co-authored-by: Cursor <[email protected]>
* fix(filters): invoke Array.prototype methods on unsanitized array values
Call built-ins via Array.prototype.<m>.call(...) for values that come
from scope (join, compact, concat, slice, where/reject) so an overridden
instance method on unsanitized data cannot hijack filter behavior.
Methods on freshly-created arrays are left as-is.
Co-authored-by: Cursor <[email protected]>
* fix(filters): use String.prototype.slice for the string branch of slice
Route the non-array branch through String.prototype.slice.call so the
slice filter never dispatches through a possibly-overridden instance
method, matching the Array.prototype guard.
Co-authored-by: Cursor <[email protected]>
---------
Co-authored-by: Cursor <[email protected]>
The `modulo` filter used JavaScript's `%` (truncated remainder, sign
follows the dividend). Shopify/Ruby Liquid uses floored modulo, where the
result takes the sign of the divisor. Since liquidjs advertises Shopify
compatibility, negative operands produced the wrong sign.
Use `((v % arg) + arg) % arg` to match Ruby's `%`. Positive-operand
results are unchanged.
* docs(readme): lead with quick start and scannable structure
Restructure the README to match common OSS conventions: tagline and
badges above the fold, copy-paste Quick start, Features list, and a
compact Used by section. Remove the star plea, centered logo, and
per-project marketing blurbs that pushed useful content down.
Co-authored-by: Cursor <[email protected]>
* docs(readme): playground GIF, used-by grid, and docs homepage sync
Add data/used-by.json with build:used-by for README and docs homepage, playground demo capture, and shared home-section layout. Used by lists products with site logos; Financial Support keeps org and individual sponsors.
Co-authored-by: Cursor <[email protected]>
* chore: use .local for playground capture scratch files
Co-authored-by: Cursor <[email protected]>
* fix: satisfy eslint in build-used-by and capture scripts
Co-authored-by: Cursor <[email protected]>
* chore: drop one-off playground capture script
Co-authored-by: Cursor <[email protected]>
* refactor(docs): copy Used by from README like financial contributors
Drop data/used-by.json and build-used-by.js; build-contributors.js now extracts USED-BY-BEGIN/END to used-by.swig.
Co-authored-by: Cursor <[email protected]>
* refactor(docs): inline Used by section, drop home-section partial
Co-authored-by: Cursor <[email protected]>
* fix(docs): drop redundant logo styles from .contributors
Co-authored-by: Cursor <[email protected]>
* fix(docs): build liquid bundle before hexo serve
Co-authored-by: Cursor <[email protected]>
* refactor(docs): drop playground window chrome from capture demo
Co-authored-by: Cursor <[email protected]>
* refactor(docs): revert playground capture changes to master behavior
Restore Ace output pane, drop Prism and output-preview styling. Simplify docs:dev to rely on docs prestart.
Co-authored-by: Cursor <[email protected]>
* refactor(docs): rely on docs prebuild for liquid bundle and contributors
Co-authored-by: Cursor <[email protected]>
* feat(docs): show playground output as Prism-highlighted HTML code
Co-authored-by: Cursor <[email protected]>
* feat(docs): polish playground layout and regenerate README demo GIF
* fix(docs): align playground GIF capture with live editor styling
* fix(docs): unify playground pane padding and hold output on errors
Match editor inset to the output panel, drop Prism from output preview,
keep the last render while typing invalid template/context, and refresh
the README demo GIF.
* fix(docs): regenerate playground GIF with held output during typing
* feat(docs): sync Used by logos and polish playground
Inline README Used by grid on the docs homepage, refine playground layout and live output behavior, and drop the unused build-used-by script from package scripts.
Co-authored-by: Cursor <[email protected]>
* fix(docs): restore Rock RMS logo and remove duplicate entry
Restore the official Rock RMS wordmark (GetImage.ashx?id=72534) instead of the SparkDevNetwork GitHub org avatar that was wrongly substituted for it.
Co-authored-by: Cursor <[email protected]>
* fix(docs): regenerate playground GIF with live indicator states
Restore the capture script for the new pane-indicator layout so the README demo shows correct idle/active/pending/ok colors and pulsing animations while typing.
* fix(docs): static playground GIF with correct indicator colors
Capture one frame per keystroke with animations disabled so dot states
(idle/active/pending/ok) match the live playground without pulsing.
Co-authored-by: Cursor <[email protected]>
* docs: use square Rock RMS icon in Used by section
Co-authored-by: Cursor <[email protected]>
* fix(docs): ensure capture indicator colors apply instantly
Disable indicator transitions and cancel active animations before
setting data-state so pending yellow is not stuck on the prior ok green.
Co-authored-by: Cursor <[email protected]>
* docs: point Microsoft Used by link to microsoft.com
The merged tile title covers Power Pages and Azure API Management; href should go to Microsoft home, not Power Pages only.
Co-authored-by: Cursor <[email protected]>
* docs: remove Dailycontributors from Used by section
No evidence they run on LiquidJS; they are an OpenCollective sponsor only.
Co-authored-by: Cursor <[email protected]>
* docs: reword intro to say Liquid, not Shopify Liquid
Move Shopify into the compatibility list and drop the shopify/liquid link from README; align package.json description.
Co-authored-by: Cursor <[email protected]>
* fix(docs): restore playground output as Prism-highlighted HTML
Co-authored-by: Cursor <[email protected]>
* docs: add extensible to README intro and package description
EOF
Co-authored-by: Cursor <[email protected]>
* chore: move playground capture script to .local
Co-authored-by: Cursor <[email protected]>
* fix(docs): drop unused Ace mode-html from playground
Output pane uses Prism, not Ace; template and context editors still need liquid/json modes and basePath for themes.
Co-authored-by: Cursor <[email protected]>
* docs: sync intro sentence across package and site metadata
Align package.json, docs config, manifest, llms.txt, and AGENTS.md tagline to the README canonical description.
Co-authored-by: Cursor <[email protected]>
* docs: trim verbose intro in intro-to-liquid tutorial
Remove README tagline and repo-purpose copy duplicated by the recent metadata sync.
Co-authored-by: Cursor <[email protected]>
* docs: shorten homepage banner subtitle
Trim docs site banner and short taglines after the em dash; keep full description for meta tags and npm/README.
* docs: simplify playground GIF caption in README
Co-authored-by: Cursor <[email protected]>
* docs: dedupe homepage subtitle and description into _config.yml
Remove redundant front matter from index.pug; theme falls back to site config for banner and meta tags.
Co-authored-by: Cursor <[email protected]>
---------
Co-authored-by: Cursor <[email protected]>
The Tokenizer constructor calls createTrie(operators) and
createTrie(literalValues) on every instantiation, and liquidjs builds a
fresh Tokenizer per output/tag while parsing. On typical templates this
rebuilt the same prefix-tries dozens of times and showed up as a large
share of parse CPU in profiling.
Memoize createTrie with a module-level WeakMap keyed on the input object.
The inputs (operators, literalValues) are stable references and the trie
is only ever read afterward (via matchTrie), never mutated, so caching by
reference is behavior-preserving. WeakMap (not Map) lets short-lived,
per-instance operator objects and their tries be garbage collected.
Replace the private email contact with GitHub Security Advisories and
set the common-case fix expectation to within a month.
Co-authored-by: Cursor <[email protected]>
* fix(security): charge pop filter allocation to memoryLimit (CWE-770)
The `pop` array filter cloned the input via `[...toArray(v)]` without
charging `this.context.memoryLimit.use(...)`, bypassing the memoryLimit
DoS guard that its sibling filters (shift, unshift, compact, etc.) apply.
Mirror `shift` to account for the O(N) allocation.
Co-authored-by: Cursor <[email protected]>
* fix(security): charge sample filter full clone allocation to memoryLimit (CWE-770)
Co-authored-by: Cursor <[email protected]>
---------
Co-authored-by: Cursor <[email protected]>
* docs: polish theme, playground, and reference pages
Improve readability of the docs site with updated light/dark tokens, shared
code-block styling, and playground editors that follow system color scheme.
Skip CookieHub on localhost, serve the browser bundle from theme source, and
use backtick titles on filter/tag reference pages for consistent navigation.
Co-authored-by: Cursor <[email protected]>
* docs: highlight npx in bash blocks and polish English copy
Use Prism insertBefore for CLI commands like npx, tighten tutorial and reference wording, and keep YAML titles free of backticks so sidebar and page headings stay correct.
Co-authored-by: Cursor <[email protected]>
* docs: restore lowercase filter and tag titles
Titles should match actual filter/tag identifiers (e.g. abs, append), not capitalized English labels.
Co-authored-by: Cursor <[email protected]>
---------
Co-authored-by: Cursor <[email protected]>
* docs: add GitHub buttons and improve option docs
* chore: replace husky with prepush check
* docs: revamp homepage and switch to custom GitHub buttons
- Make the docs English-only by removing all zh-cn content, the language switcher UI, and related JS/config
- Rework homepage feature cards (Safe & Typed, Pure JavaScript, Shopify & Jekyll, Streaming) and refresh section colors/layout
- Replace buttons.github.io with custom Star/Sponsor buttons featuring a live star count and dark-mode support
- Drop the buttons.js script and tidy banner, header, footer, and share partials
Co-authored-by: Cursor <[email protected]>
* fix: restore tsconfig settings and changelog build
Re-add suppressImplicitAnyIndexErrors and downlevelIteration removed in
a75033e2c, which broke the rollup TypeScript build on CI. Drop zh-cn
changelog output now that translations were removed.
Co-authored-by: Cursor <[email protected]>
* fix: resolve TS errors without deprecated tsconfig options
Co-authored-by: Cursor <[email protected]>
---------
Co-authored-by: Cursor <[email protected]>
- Add createScope() building Object.create(null) with optional own props
- Initialize context stack bottom with createScope() for assign/capture
- Push null-proto scopes from for, tablerow, block, layout, include (incl. Jekyll)
Co-authored-by: Cursor <[email protected]>
* fix(security): block Object.prototype filter/tag lookups (RCE)
`liquid.filters` and `liquid.tags` were plain `{}` so bracket access on
template-controlled keys inherited from `Object.prototype`. Most damaging:
`{{ x | valueOf }}` resolved to `Object.prototype.valueOf`, which the
filter pipeline called as a handler with `this = FilterImpl`; valueOf
returns its receiver, leaking `context`, `liquid`, `token` (and via them
parser, loader, fs) into the template — chain that with `group_by`/`where`
gadgets and an attacker reaches `Function`/`child_process` for RCE.
Same shape on the tag side: `{% constructor %}` bypassed the
"tag not found" assertion and crashed with a confusing message.
Use null-prototype storage so `liquid.filters[name]` / `liquid.tags[name]`
only resolve to explicitly registered entries. The existing
`assert(impl || !strictFilters)` and `assert(TagClass, ...)` now do the
right thing for `valueOf`, `toString`, `constructor`, `__proto__`,
`hasOwnProperty`, `isPrototypeOf`, `__defineGetter__`, etc.
Co-authored-by: Cursor <[email protected]>
* test: fold prototype-registry regressions into register + e2e
Co-authored-by: Cursor <[email protected]>
* test: assert null-prototype registries vs all Object.prototype keys
Co-authored-by: Cursor <[email protected]>
* test: dedupe registry checks; merge filter prototype loop
Co-authored-by: Cursor <[email protected]>
* fix(context): use null-prototype scope and register objects
Add createScope(); use for bottom scope, spawn default, getAll merge, ctx.push frames, filter loops, include/layout blocks registers, and cycle groups. registers uses Object.create(null) and getRegister uses ??.
For-loop continue register defaults to 0 (not {}): Array.slice coerces plain {} but not null-prototype objects.
Export createScope from the package entry.
Co-authored-by: Cursor <[email protected]>
* revert(context): plain {} registers and getRegister ||
Registers are only mutated by tag implementations, not templates; keep null-prototype scopes/createScope for push frames.
Co-authored-by: Cursor <[email protected]>
* test(context): assert scope isolation without probing prototypes
Replace Object.getPrototypeOf checks for bottom() and getAll() with
'in' checks on typical Object.prototype names plus a merge assertion.
Co-authored-by: Cursor <[email protected]>
* test(e2e): assert constructor filter/tag lookups (node + UMD)
Co-authored-by: Cursor <[email protected]>
* test(context): cover Object.prototype keys under ownPropertyOnly
- Add getSync cases for constructor and valueOf on plain objects
- Remove scope storage tests that used the in operator
Co-authored-by: Cursor <[email protected]>
* refactor: remove createScope helper
Drop the exported helper and finish migrating call sites. Revert incidental context/for/include/layout churn so behavior matches mainline aside from the removal. Trim duplicate e2e and heavy Object.prototype loops in registry tests.
Co-authored-by: Cursor <[email protected]>
* docs: document ownPropertyOnly and Drop security in security model
Co-authored-by: Cursor <[email protected]>
* docs(zh-cn): sync security model with ownPropertyOnly and Drop notes
Co-authored-by: Cursor <[email protected]>
---------
Co-authored-by: Cursor <[email protected]>