Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option to disable the code execution from string (eval and function) #605

Open
simontom opened this issue Oct 14, 2024 · 3 comments
Open
Assignees
Labels

Comments

@simontom
Copy link

Hey folks,
our scenario is "customer scriptable multitenant" runtime. We want to be very cautious what to allow.

In order to make it a bit more safer,
we need the ClearScript API to disable eval and other ECMAScript APIs that convert strings into code (e.g., the Function CTOR).

I see there is some flag in V8 (pro'ly) resulting right in the behaviour we crave for: "--disallow-code-generation-from-strings"

We were able "disable" eval exectuting the following script:

eval = function() {
    throw new Error("The 'eval' function is disabled.");
}

This is what I've tried with function CTOR so far

test('use function for code generation', async (t) => {
    // Has no effect
    (function () { }).constructor = null;
    // so ....
    // These are the main problem
    const FuncCtor = (function () { }).constructor;
    const AsyncFuncCtor = (async function () { }).constructor;
    
    const log1 = new FuncCtor('str', 'console.log(str);');
    log1('Hello, world! 1');

    const fetchURL = new AsyncFuncCtor('url', 'return await fetch(url);');
    await fetchURL("https://www.google.com")
        .then((res) => res.text())
        .then((text) => text.slice(0, 100))
        .then(console.log);

    // This is fine, throws (but the lines above....)
    Function = null;
    const log2 = new Function('str', 'console.log(str);');
    log2('Hello, world! 2');
});
@ClearScriptLib
Copy link
Collaborator

Hi @simontom,

How about something like this:

engine.Execute(@"(() => {
    const AsyncFunction = (async () => {}).constructor;
    const ctor = (function () { throw new Error('Function constructors are disabled'); }).bind();
    Object.defineProperty(Function.prototype, 'constructor', { value: ctor });
    Object.defineProperty(AsyncFunction.prototype, 'constructor', { value: ctor });
    Function = new Proxy(Function, { construct: ctor, apply: ctor });
})()");

Please let us know if that works for you. Thanks!

@simontom
Copy link
Author

Alright, folks, it seems it's working like a charm 🪄 💚

test('test 1', async (t) => {
    // Disable Function constructor (works like a charm)
    const AsyncFunction = (async () => {}).constructor;
    const newThrowingCtor = (function () {
        throw new Error('Function constructors are disabled');
    }).bind();
    Object.defineProperty(Function.prototype, 'constructor', {value: newThrowingCtor});
    Object.defineProperty(AsyncFunction.prototype, 'constructor', {value: newThrowingCtor});
    Function = new Proxy(Function, {construct: newThrowingCtor, apply: newThrowingCtor});

    try {
        const FuncCtor = (function () {}).constructor;
        
        const log = new FuncCtor('str', 'console.log(str);');
        log('Hello, world! 1');
    } catch (e) {
        console.log(e);
    }

    try {
        let AsyncFuncCtor = (async function () {}).constructor;

        const fetchURL = new AsyncFuncCtor('url', 'return await fetch(url);');
        await fetchURL("https://www.google.com")
            .then((res) => res.text())
            .then((text) => text.slice(0, 100))
            .then(console.log);
    } catch (e) {
        console.log(e);
    }

    try {
        const log = new Function('str', 'console.log(str);');
        log('Hello, world! 2');
    } catch (e) {
        console.log(e);
    }
});
Error: Function constructors are disabled
    at new <anonymous> (/TypeScriptWorkbench/test/eval-and-function.test.mjs:31:15)
    at TestContext.<anonymous> (/TypeScriptWorkbench/test/eval-and-function.test.mjs:40:21)
    at Test.runInAsyncScope (node:async_hooks:206:9)
    at Test.run (node:internal/test_runner/test:631:25)
    at Test.processPendingSubtests (node:internal/test_runner/test:374:18)
    at Test.postRun (node:internal/test_runner/test:715:19)
    at Test.run (node:internal/test_runner/test:673:12)
    at async startSubtest (node:internal/test_runner/harness:216:3)
Error: Function constructors are disabled
    at new <anonymous> (/TypeScriptWorkbench/test/eval-and-function.test.mjs:31:15)
    at TestContext.<anonymous> (/TypeScriptWorkbench/test/eval-and-function.test.mjs:49:26)
    at Test.runInAsyncScope (node:async_hooks:206:9)
    at Test.run (node:internal/test_runner/test:631:25)
    at Test.processPendingSubtests (node:internal/test_runner/test:374:18)
    at Test.postRun (node:internal/test_runner/test:715:19)
    at Test.run (node:internal/test_runner/test:673:12)
    at async startSubtest (node:internal/test_runner/harness:216:3)
Error: Function constructors are disabled
    at file:///C:/_git/slack/prodaas/TypeScriptWorkbench/test/eval-and-function.test.mjs:31:15
    at TestContext.<anonymous> (/TypeScriptWorkbench/test/eval-and-function.test.mjs:59:21)
    at Test.runInAsyncScope (node:async_hooks:206:9)
    at Test.run (node:internal/test_runner/test:631:25)
    at Test.processPendingSubtests (node:internal/test_runner/test:374:18)
    at Test.postRun (node:internal/test_runner/test:715:19)
    at Test.run (node:internal/test_runner/test:673:12)
    at async startSubtest (node:internal/test_runner/harness:216:3)

IMHO, an option / flag might be a better solution for future handling of such security 💡

@simontom
Copy link
Author

Thanks a bunch for your lightning-fast help 🙇

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants