Skip to content

fix: guard AST node property lookups against prototype pollution (fixes #3414) - #3470

Open
Ayoub-glitsh wants to merge 2 commits into
pugjs:masterfrom
Ayoub-glitsh:fix/prototype-pollution-rce-gadgets
Open

Ayoub-glitsh wants to merge 2 commits into
pugjs:masterfrom
Ayoub-glitsh:fix/prototype-pollution-rce-gadgets

Conversation

@Ayoub-glitsh

Copy link
Copy Markdown

Problem

Several packages accessed properties on AST node objects without verifying they were own properties. In JavaScript, obj.prop traverses the prototype chain — so if an attacker has already achieved prototype pollution elsewhere in the app, they can inject values like Object.prototype.block, .type, .val, .code, .line and escalate to RCE during template compilation.

The three PoC gadgets from #3414 all exploit this pattern:

  • Gadget 1: Object.prototype.block with a malicious Text node containing arbitrary JS in line
  • Gadget 2: Object.prototype.code with a malicious val
  • Gadget 3: Object.prototype.val injecting code into attribute generation

Fix

Guard all property accesses that influence control flow or code generation with Object.prototype.hasOwnProperty.call(node, 'prop') before reading the value.

Files changed

  • pug-walk/index.js — the most critical, used by all other packages. switch (ast.type) and all if (ast.block/alternate/consequent) checks are now guarded.
  • pug-code-gen/index.js — node.line, node.block, tag.code, node.isInline, node.val, node.type checks guarded.
  • pug-linker/index.js — node.type, node.call, node.mode, node.block, node.ignore, node.parents, node.textOnly checks guarded.
  • pug-filters/lib/handle-filters.js — node.type, node.filters, node.block checks guarded.

Tests

648/650 tests pass. The 2 failures are pre-existing and unrelated — they test for old Node.js error message wording (Cannot read property vs the newer Cannot read properties) introduced in Node 16+.

Unsafe property accesses like if (node.block) or
ode.type === 'X'
traverse the prototype chain, allowing an attacker who has already
achieved prototype pollution to escalate to RCE by injecting values
such as Object.prototype.block, .type, .val, .code, .line, etc.

Fix all vulnerable lookup sites in pug-walk, pug-code-gen, pug-linker,
and pug-filters by using Object.prototype.hasOwnProperty.call() before
reading any property that influences control flow or code generation.

Fixes pugjs#3414
@rollingversions

rollingversions Bot commented Apr 29, 2026 •

Copy link
Copy Markdown

This PR will not result in a new version of the following packages as there are no user facing changes:

  • pug
  • pug-attrs
  • pug-code-gen
  • pug-error
  • pug-filters
  • pug-lexer
  • pug-linker
  • pug-load
  • pug-parser
  • pug-runtime
  • pug-strip-comments
  • pug-walk

Add changes to trigger a release

Change log has not been updated since latest commit Update Changelog

@Ayoub-glitsh

Copy link
Copy Markdown
Author

@RollingVersions bump

pug-walk

security

pug-code-gen

security

pug-linker

security

pug-filters

security

- Add hasOwnProperty guard for mixin.code in parser (fixes issue pugjs#3414 path C)
- Add hasOwnProperty guard for valueResponse.val in lexer (fixes issue pugjs#3414 path D)
- Add comprehensive security regression tests for all four vulnerability paths
- Ensure normal functionality is preserved for legitimate use cases

This completes the security fix addressing all four exploitation paths:
1. code.block in code generation (already fixed in base branch)
2. tag.code in code generation (already fixed in base branch)
3. mixin.code in parser (fixed in this commit)
4. valueResponse.val in lexer (fixed in this commit)

The fix prevents prototype pollution escalation to RCE by using
Object.prototype.hasOwnProperty.call() to ensure only own properties
are used for control flow and code generation decisions.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant