Skip to content

feat(bug-detectors): replace RCE substring check with global canary#873

Open
oetr wants to merge 1 commit intomainfrom
improve-RCE
Open

feat(bug-detectors): replace RCE substring check with global canary#873
oetr wants to merge 1 commit intomainfrom
improve-RCE

Conversation

@oetr
Copy link
Copy Markdown
Contributor

@oetr oetr commented Apr 1, 2026

The old detector checked whether the target string appeared in eval/Function source text. This false-positived on safely quoted occurrences (e.g. Handlebars' lookupProperty(d, "jaz_zer")).

Install a canary function on globalThis that fires only when attacker-controlled code actually executes. The before-hooks now provide fuzzing guidance only (guideTowardsContainment). The canary is propagated to the Jest vm.Context sandbox, following the same pattern as the prototype-pollution detector.

@oetr oetr marked this pull request as ready for review April 1, 2026 11:48
Copilot AI review requested due to automatic review settings April 1, 2026 11:48
@oetr oetr marked this pull request as draft April 1, 2026 11:51
@oetr oetr changed the base branch from esm-support to main April 1, 2026 11:52
@oetr oetr marked this pull request as ready for review April 1, 2026 11:52
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Remote Code Execution bug detector to avoid false positives from substring checks by introducing a global canary on globalThis and ensuring it’s also visible inside Jest’s vm.Context sandbox.

Changes:

  • Replace source-text substring detection for eval/Function with a global canary-based detection mechanism.
  • Update before-hooks to provide only fuzzing guidance (guideTowardsContainment) instead of reporting findings directly.
  • Expand and rename fuzz targets/tests to cover canary-triggering cases and “safe” (non-triggering) cases, including string-literal and string-coercible bodies.

Reviewed changes

Copilot reviewed 61 out of 63 changed files in this pull request and generated 2 comments.

File Description
tests/bug-detectors/remote-code-execution/tests.fuzz.js Renames/reorganizes fuzz test cases to distinguish canary-triggering vs safe scenarios.
tests/bug-detectors/remote-code-execution/fuzz.js Updates fuzz entrypoints to execute code that should (or should not) trigger the canary for eval and Function.
tests/bug-detectors/remote-code-execution.test.js Aligns CLI + Jest integration tests with the new entrypoints and expected outcomes.
packages/bug-detectors/internal/remote-code-execution.ts Implements the global canary, propagates it to Jest vmContext, and changes hooks to guidance-only.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/bug-detectors/internal/remote-code-execution.ts
Comment thread packages/bug-detectors/internal/remote-code-execution.ts Outdated
@oetr oetr requested a review from kyakdan April 1, 2026 19:20
The old detector checked whether the target string appeared in
eval/Function source text. This false-positived on safely quoted
occurrences (e.g. handlebars' lookupProperty(d, "jaz_zer")).

Install a canary function on globalThis that fires only when
attacker-controlled code actually executes. The before-hooks now
provide fuzzing guidance only (guideTowardsContainment). The canary
is propagated to the Jest vm.Context sandbox, following the same
pattern as the prototype-pollution detector.
@kyakdan
Copy link
Copy Markdown
Member

kyakdan commented Apr 14, 2026

The current design is better than looking for "jaz_zer" substring in the source text for eval()/Function(). However, I think that only checking the getter can still produce false positives. For example, the following code snippet would trigger the RCE bug detector without eval()/Function() being involved.

const propertyName = "jaz_zer";
void globalThis[propertyName];
console.log("can be called just fine");

I think that the current “if anything reads globalThis.jaz_zer, report RCE.” check is too borad. How about we create a cannary call, e.g., jaz_zer() and guide fuzzing to generate such calls as arguments for eval() or Function(). Calling this function can then report an RCE bug.

@oetr
Copy link
Copy Markdown
Contributor Author

oetr commented Apr 14, 2026

I think we should make it configurable and allow both (as in either or) defaulting to the current implementation that marks access as RCE.

And the reasoning is that it is:

  1. very unusual if something accesses globalThis["jaz_zer"]
  2. the actuall execution might happen later, behind some fuzzing blocker. E.g.:
var fn = eval(my_awesome_sanitizer("..."));
...
 // much later, after lots of fuzzing
fn(); // actual call

In case it's a false-positive, the users can be informed that it's possible to switch to a more strict RCE detection that requires an actual canary execution.
WDYT?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants