Arbitrary code execution in less-openui5
Description
less-openui5 before 0.10.0 executes JavaScript embedded in untrusted Less theme files, enabling code injection during builds.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
less-openui5 before 0.10.0 executes JavaScript embedded in untrusted Less theme files, enabling code injection during builds.
## Vulnerability less-openui5 is an npm package used to build OpenUI5 themes with Less.js. Prior to version 0.10.0, it used a fork of Less.js v1.6.3 that enabled inline JavaScript evaluation. When processing untrusted .less files, any embedded JavaScript code would be executed during the build process [1][2]. This behavior, while a feature of older Less.js versions, was unexpected in OpenUI5/SAPUI5 development.
Exploitation
An attacker could craft a malicious library or theme containing a .less file with hidden JavaScript, such as backtick-delimited code. If a developer or CI system uses less-openui5 to process this resource (e.g., via UI5 Tooling), the JavaScript runs with the privileges of the build process [2]. No authentication is required beyond access to the build pipeline.
Impact
Successful exploitation allows arbitrary code execution within the build environment. An attacker could steal credentials, modify source code, or pivot to internal systems. The vulnerability is particularly severe in CI/CD contexts where many developers share build resources.
Mitigation
The fix was released in less-openui5 version 0.10.0, which completely removes inline JavaScript evaluation from the Less.js fork [3][4]. Users should update to this version immediately. There is no workaround other than avoiding processing untrusted .less files.
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
less-openui5npm | < 0.10.0 | 0.10.0 |
Affected products
2Patches
1c0d3a8572974[BREAKING] Security: Disable JavaScript execution in Less.js
10 files changed · +144 −75
lib/thirdparty/less/env.js+3 −1 modified@@ -14,7 +14,9 @@ 'compress', // option - whether to compress 'processImports', // option - whether to process imports. if false then imports will not be imported 'syncImport', // option - whether to import synchronously - 'javascriptEnabled',// option - whether JavaScript is enabled. if undefined, defaults to true + /* BEGIN MODIFICATION */ + // Removed 'javascriptEnabled' + /* END MODIFICATION */ 'mime', // browser only - mime type for sheet import 'useFileCache', // browser only - whether to use the per file session cache 'currentFileInfo' // information about the current file - for error reporting and importing and making urls relative etc.
lib/thirdparty/less/functions.js+5 −1 modified@@ -213,7 +213,11 @@ tree.functions = { } }, e: function (str) { - return new(tree.Anonymous)(str instanceof tree.JavaScript ? str.evaluated : str); + /* BEGIN MODIFICATION */ + // Removed handling of tree.JavaScript + return new(tree.Anonymous)(str); + /* END MODIFICATION */ + }, escape: function (str) { return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29"));
lib/thirdparty/less/index.js+3 −1 modified@@ -112,7 +112,9 @@ require('./tree/mixin'); require('./tree/comment'); require('./tree/anonymous'); require('./tree/value'); -require('./tree/javascript'); +/* BEGIN MODIFICATION */ +// Removed require('./tree/javascript'); +/* END MODIFICATION */ require('./tree/assignment'); require('./tree/condition'); require('./tree/paren');
lib/thirdparty/less/lessc_helper.js+3 −1 modified@@ -31,7 +31,9 @@ var lessc_helper = { console.log(" -M, --depends Output a makefile import dependency list to stdout"); console.log(" --no-color Disable colorized output."); console.log(" --no-ie-compat Disable IE compatibility checks."); - console.log(" --no-js Disable JavaScript in less files"); + /* BEGIN MODIFICATION */ + // Removed --no-js option + /* END MODIFICATION */ console.log(" -l, --lint Syntax check only (lint)."); console.log(" -s, --silent Suppress output of error messages."); console.log(" --strict-imports Force evaluation of imports.");
lib/thirdparty/less/parser.js+7 −11 modified@@ -993,27 +993,23 @@ less.Parser = function Parser(env) { } }, + /* BEGIN MODIFICATION */ + // Removed support for javascript + // - // JavaScript code to be evaluated + // JavaScript code (disabled) // // `window.location.href` // javascript: function () { - var str, j = i, e; + var j = i, e; if (input.charAt(j) === '~') { j++; e = true; } // Escaped strings if (input.charAt(j) !== '`') { return; } - if (env.javascriptEnabled !== undefined && !env.javascriptEnabled) { - error("You are using JavaScript, which has been disabled."); - } - - if (e) { $char('~'); } - str = $re(/^`([^`]*)`/); - if (str) { - return new(tree.JavaScript)(str[1], i, e); - } + error("You are using JavaScript, which has been disabled."); } + /* END MODIFICATION */ }, //
lib/thirdparty/less/README.md+4 −0 modified@@ -3,3 +3,7 @@ This folder contains the `lib/less` sub-folder of [v1.6.3](https://github.com/less/less.js/tree/v1.6.3/lib/less) of the [less.js project](https://github.com/less/less.js) with commit [`ccd8ebbfdfa300b6e748e8d7c12e3dbb0efd8371`](https://github.com/less/less.js/commit/ccd8ebbfdfa300b6e748e8d7c12e3dbb0efd8371) applied on top of it to resolve https://github.com/SAP/less-openui5/issues/24. The files `browser.js` and `rhino.js` have been removed, as they are not relevant for the Node.js implementation. + +The file `tree/javascript.js` has been removed to disable JavaScript execution. + +Modifications within the files are marked with `/* BEGIN MODIFICATION */` and `/* END MODIFICATION */` comments.
lib/thirdparty/less/tree/javascript.js+0 −58 removed@@ -1,58 +0,0 @@ -(function (tree) { - -tree.JavaScript = function (string, index, escaped) { - this.escaped = escaped; - this.expression = string; - this.index = index; -}; -tree.JavaScript.prototype = { - type: "JavaScript", - eval: function (env) { - var result, - that = this, - context = {}; - - var expression = this.expression.replace(/@\{([\w-]+)\}/g, function (_, name) { - return tree.jsify(new(tree.Variable)('@' + name, that.index).eval(env)); - }); - - try { - expression = new(Function)('return (' + expression + ')'); - } catch (e) { - throw { message: "JavaScript evaluation error: " + e.message + " from `" + expression + "`" , - index: this.index }; - } - - var variables = env.frames[0].variables(); - for (var k in variables) { - if (variables.hasOwnProperty(k)) { - /*jshint loopfunc:true */ - context[k.slice(1)] = { - value: variables[k].value, - toJS: function () { - return this.value.eval(env).toCSS(); - } - }; - } - } - - try { - result = expression.call(context); - } catch (e) { - throw { message: "JavaScript evaluation error: '" + e.name + ': ' + e.message.replace(/["]/g, "'") + "'" , - index: this.index }; - } - if (typeof(result) === 'number') { - return new(tree.Dimension)(result); - } else if (typeof(result) === 'string') { - return new(tree.Quoted)('"' + result + '"', result, this.escaped, this.index); - } else if (Array.isArray(result)) { - return new(tree.Anonymous)(result.join(', ')); - } else { - return new(tree.Anonymous)(result); - } - } -}; - -})(require('../tree')); -
lib/thirdparty/less/tree/quoted.js+7 −1 modified@@ -22,7 +22,13 @@ tree.Quoted.prototype = { eval: function (env) { var that = this; var value = this.value.replace(/`([^`]+)`/g, function (_, exp) { - return new(tree.JavaScript)(exp, that.index, true).eval(env).value; + /* BEGIN MODIFICATION */ + // Removed support for javascript + const error = new Error("You are using JavaScript, which has been disabled."); + error.index = that.index; + error.type = "Syntax"; + throw error; + /* END MODIFICATION */ }).replace(/@\{([\w-]+)\}/g, function (_, name) { var v = new(tree.Variable)('@' + name, that.index, that.currentFileInfo).eval(env, true); return (v instanceof tree.Quoted) ? v.value : v.toCSS();
README.md+4 −1 modified@@ -149,7 +149,10 @@ lib2 Type: `object` Options for the [less](http://lesscss.org) parser (`less.Parser`). -**Note:** Default of `relativeUrls` option is changed from `false` to `true`. + +**Note** +- Default of `relativeUrls` option is changed from `false` to `true`. +- Option `javascriptEnabled` has been removed. JavaScript is always disabled and cannot be enabled. ##### compiler
test/test.js+108 −0 modified@@ -419,6 +419,114 @@ describe("error handling", function() { assert.ok(err); }); }); + + it("should throw error when using inline JavaScript", function() { + const lessInput = `.rule { + @var: \`(function(){ return "Cat"; })()\`; + color: @var; +}`; + return new Builder().build({ + lessInput + }).then(function(res) { + // no resolve + assert.ok(false, `Expected build to fail but finished with content:\n${res.css}`); + }, function(err) { + assert.equal(err.message, "You are using JavaScript, which has been disabled."); + assert.ok(err); + }); + }); + + it("should throw error when using quoted inline JavaScript", function() { + const lessInput = `.rule { + @var: "\`(function(){ return 'Cat'; })()\`"; + color: @var; +}`; + return new Builder().build({ + lessInput + }).then(function(res) { + // no resolve + assert.ok(false, `Expected build to fail but finished with content:\n${res.css}`); + }, function(err) { + assert.equal(err.message, "You are using JavaScript, which has been disabled."); + assert.ok(err); + }); + }); + + it("should throw error when using inline JavaScript with parser option javascriptEnabled: true", function() { + const lessInput = `.rule { + @var: \`(function(){ return "Cat"; })()\`; + color: @var; +}`; + return new Builder().build({ + lessInput, + parser: { + javascriptEnabled: true + } + }).then(function(res) { + // no resolve + assert.ok(false, `Expected build to fail but finished with content:\n${res.css}`); + }, function(err) { + assert.equal(err.message, "You are using JavaScript, which has been disabled."); + assert.ok(err); + }); + }); + + it("should throw error when using quoted inline JavaScript with parser option javascriptEnabled: true", function() { + const lessInput = `.rule { + @var: "\`(function(){ return 'Cat'; })()\`"; + color: @var; +}`; + return new Builder().build({ + lessInput, + parser: { + javascriptEnabled: true + } + }).then(function(res) { + // no resolve + assert.ok(false, `Expected build to fail but finished with content:\n${res.css}`); + }, function(err) { + assert.equal(err.message, "You are using JavaScript, which has been disabled."); + assert.ok(err); + }); + }); + + it("should throw error when using inline JavaScript with parser option javascriptEnabled: false", function() { + const lessInput = `.rule { + @var: \`(function(){ return "Cat"; })()\`; + color: @var; +}`; + return new Builder().build({ + lessInput, + parser: { + javascriptEnabled: false + } + }).then(function(res) { + // no resolve + assert.ok(false, `Expected build to fail but finished with content:\n${res.css}`); + }, function(err) { + assert.equal(err.message, "You are using JavaScript, which has been disabled."); + assert.ok(err); + }); + }); + + it("should throw error when using quoted inline JavaScript with parser option javascriptEnabled: false", function() { + const lessInput = `.rule { + @var: "\`(function(){ return 'Cat'; })()\`"; + color: @var; +}`; + return new Builder().build({ + lessInput, + parser: { + javascriptEnabled: false + } + }).then(function(res) { + // no resolve + assert.ok(false, `Expected build to fail but finished with content:\n${res.css}`); + }, function(err) { + assert.equal(err.message, "You are using JavaScript, which has been disabled."); + assert.ok(err); + }); + }); }); function assertLessToRtlCssEqual(filename) {
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-3crj-w4f5-gwh4ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-21316ghsaADVISORY
- lesscss.org/usage/ghsax_refsource_MISCWEB
- github.com/SAP/less-openui5/commit/c0d3a8572974a20ea6cee42da11c614a54f100e8ghsax_refsource_MISCWEB
- github.com/SAP/less-openui5/releases/tag/v0.10.0ghsax_refsource_MISCWEB
- github.com/SAP/less-openui5/security/advisories/GHSA-3crj-w4f5-gwh4ghsax_refsource_CONFIRMWEB
- www.npmjs.com/package/less-openui5ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.