Summary
When jdists processes a file, a crafted trigger attribute in a <!--jdists ... --> block is evaluated as JavaScript via new Function. Any file processed by jdists can therefore execute arbitrary code with the build process's privileges.
Root cause
lib/scope.js, around line 705, passes the block's trigger attribute to execTrigger, which evaluates it:
return new Function('return (' + trigger.replace(/(@|#|:)([\w-_]+)/g, ...) + ')')
No allowlist, no sandbox.
PoC (verified on jdists@2.2.4)
const jdists = require('jdists')
const fs = require('fs')
const content = '<!--jdists trigger="(require(\'fs\').writeFileSync(\'pwned.txt\',\'PWNED\'),true)"-->keep<!--/jdists-->'
jdists.build(content, { fromString: true, remove: '' })
// build output: "keep"
// pwned.txt is created — arbitrary code executed
Impact
- Processing an untrusted file (third-party source, vendored code, CI artifact, downloaded template) executes arbitrary code with the build process privileges.
- Amplifiers:
global.require = require in lib/jdists.js exposes the full module system to the evaluated code.
- Supply-chain scenario: a malicious file in a dependency or a crafted artifact processed in CI can compromise the build environment.
Secondary observation (not verified)
querySelector also evaluates quoted attribute values via new Function (lib/scope.js, selector parsing). It appears reachable via importation selectors; I did not fully verify a trigger path for it, so I am reporting the verified trigger vector only.
Suggested fix
- Treat
trigger as data: match against a fixed list of known trigger names (the current simple-trigger path ^([\w-_]+)(,[\w-_]+)*$ is safe) and reject anything else.
- Never evaluate file-derived strings with
new Function; if arbitrary expressions are required, use a real expression parser/sandbox.
No private vulnerability reporting channel found on this repo; reporting here. Happy to be credited if a security advisory is published.
Summary
When jdists processes a file, a crafted
triggerattribute in a<!--jdists ... -->block is evaluated as JavaScript vianew Function. Any file processed by jdists can therefore execute arbitrary code with the build process's privileges.Root cause
lib/scope.js, around line 705, passes the block'striggerattribute toexecTrigger, which evaluates it:No allowlist, no sandbox.
PoC (verified on jdists@2.2.4)
Impact
global.require = requireinlib/jdists.jsexposes the full module system to the evaluated code.Secondary observation (not verified)
querySelectoralso evaluates quoted attribute values vianew Function(lib/scope.js, selector parsing). It appears reachable via importation selectors; I did not fully verify a trigger path for it, so I am reporting the verified trigger vector only.Suggested fix
triggeras data: match against a fixed list of known trigger names (the current simple-trigger path^([\w-_]+)(,[\w-_]+)*$is safe) and reject anything else.new Function; if arbitrary expressions are required, use a real expression parser/sandbox.No private vulnerability reporting channel found on this repo; reporting here. Happy to be credited if a security advisory is published.