From 8a25b7fba94c8e9989412bc266ada307975f182d Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Thu, 2 Nov 2023 12:29:43 +0000 Subject: [PATCH] Add support for unsafe eval bindings to miniflare (#4322) * update workerd config schema * add unsafeEvalBinding to miniflare core options * add unsafeEval test * add changeset --- .changeset/tricky-otters-rescue.md | 30 +++++++++++++++++++ packages/miniflare/src/plugins/core/index.ts | 9 ++++++ .../src/runtime/config/workerd.capnp | 3 ++ .../src/runtime/config/workerd.capnp.d.ts | 4 +++ .../src/runtime/config/workerd.capnp.js | 9 ++++++ .../miniflare/src/runtime/config/workerd.ts | 1 + packages/miniflare/test/index.spec.ts | 30 ++++++++++++++++++- 7 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 .changeset/tricky-otters-rescue.md diff --git a/.changeset/tricky-otters-rescue.md b/.changeset/tricky-otters-rescue.md new file mode 100644 index 000000000000..ad128018b30f --- /dev/null +++ b/.changeset/tricky-otters-rescue.md @@ -0,0 +1,30 @@ +--- +"miniflare": minor +--- + +add `unsafeEvalBinding` option + +Add option to leverage the newly introduced [`UnsafeEval`](https://github.com/cloudflare/workerd/pull/1338) workerd binding API, +such API is used to evaluate javascript code at runtime via the provided `eval` and `newFunction` methods. + +The API, for security reasons (as per the [workers docs](https://developers.cloudflare.com/workers/runtime-apis/web-standards/#javascript-standards)), is not to be use in production but it is intended for local purposes only such as local testing. + +To use the binding you need to specify a string value for the `unsafeEvalBinding`, such will be the name of the `UnsafeEval` bindings that will be made available in the workerd runtime. + +For example the following code shows how to set the binding with the `UNSAFE_EVAL` name and evaluate the `1+1` string: + +```ts +const mf = new Miniflare({ + log, + modules: true, + script: ` + export default { + fetch(req, env, ctx) { + const two = env.UNSAFE_EVAL.eval('1+1'); + return new Response('two = ' + two); // returns 'two = 2' + } + } + `, + unsafeEvalBinding: "UNSAFE_EVAL", +}); +``` diff --git a/packages/miniflare/src/plugins/core/index.ts b/packages/miniflare/src/plugins/core/index.ts index f7e861393885..9b2e28abd300 100644 --- a/packages/miniflare/src/plugins/core/index.ts +++ b/packages/miniflare/src/plugins/core/index.ts @@ -105,6 +105,8 @@ const CoreOptionsSchemaInput = z.intersection( unsafeEphemeralDurableObjects: z.boolean().optional(), unsafeDirectHost: z.string().optional(), unsafeDirectPort: z.number().optional(), + + unsafeEvalBinding: z.string().optional(), }) ); export const CoreOptionsSchema = CoreOptionsSchemaInput.transform((value) => { @@ -314,6 +316,13 @@ export const CORE_PLUGIN: Plugin< ); } + if (options.unsafeEvalBinding !== undefined) { + bindings.push({ + name: options.unsafeEvalBinding, + unsafeEval: kVoid, + }); + } + return Promise.all(bindings); }, async getNodeBindings(options) { diff --git a/packages/miniflare/src/runtime/config/workerd.capnp b/packages/miniflare/src/runtime/config/workerd.capnp index 29f8e01c9130..0bd98b55515e 100644 --- a/packages/miniflare/src/runtime/config/workerd.capnp +++ b/packages/miniflare/src/runtime/config/workerd.capnp @@ -364,6 +364,9 @@ struct Worker { # A binding for Hyperdrive. Allows workers to use Hyperdrive caching & pooling for Postgres # databases. + unsafeEval @23 :Void; + # A simple binding that enables access to the UnsafeEval API. + # TODO(someday): dispatch, other new features } diff --git a/packages/miniflare/src/runtime/config/workerd.capnp.d.ts b/packages/miniflare/src/runtime/config/workerd.capnp.d.ts index 02d95a0dce27..60d71619aeea 100644 --- a/packages/miniflare/src/runtime/config/workerd.capnp.d.ts +++ b/packages/miniflare/src/runtime/config/workerd.capnp.d.ts @@ -470,6 +470,7 @@ export declare enum Worker_Binding_Which { FROM_ENVIRONMENT = 14, ANALYTICS_ENGINE = 15, HYPERDRIVE = 16, + UNSAFE_EVAL = 17, } export declare class Worker_Binding extends __S { static readonly UNSPECIFIED = Worker_Binding_Which.UNSPECIFIED; @@ -490,6 +491,7 @@ export declare class Worker_Binding extends __S { static readonly FROM_ENVIRONMENT = Worker_Binding_Which.FROM_ENVIRONMENT; static readonly ANALYTICS_ENGINE = Worker_Binding_Which.ANALYTICS_ENGINE; static readonly HYPERDRIVE = Worker_Binding_Which.HYPERDRIVE; + static readonly UNSAFE_EVAL = Worker_Binding_Which.UNSAFE_EVAL; static readonly Type: typeof Worker_Binding_Type; static readonly DurableObjectNamespaceDesignator: typeof Worker_Binding_DurableObjectNamespaceDesignator; static readonly CryptoKey: typeof Worker_Binding_CryptoKey; @@ -601,6 +603,8 @@ export declare class Worker_Binding extends __S { initHyperdrive(): Worker_Binding_Hyperdrive; isHyperdrive(): boolean; setHyperdrive(): void; + isUnsafeEval(): boolean; + setUnsafeEval(): void; toString(): string; which(): Worker_Binding_Which; } diff --git a/packages/miniflare/src/runtime/config/workerd.capnp.js b/packages/miniflare/src/runtime/config/workerd.capnp.js index 6d5239b7b992..85497180f4c3 100644 --- a/packages/miniflare/src/runtime/config/workerd.capnp.js +++ b/packages/miniflare/src/runtime/config/workerd.capnp.js @@ -1291,6 +1291,8 @@ var Worker_Binding_Which; "ANALYTICS_ENGINE"; Worker_Binding_Which[(Worker_Binding_Which["HYPERDRIVE"] = 16)] = "HYPERDRIVE"; + Worker_Binding_Which[(Worker_Binding_Which["UNSAFE_EVAL"] = 17)] = + "UNSAFE_EVAL"; })( (Worker_Binding_Which = exports.Worker_Binding_Which || (exports.Worker_Binding_Which = {})) @@ -1736,6 +1738,12 @@ class Worker_Binding extends capnp_ts_1.Struct { setHyperdrive() { capnp_ts_1.Struct.setUint16(0, 16, this); } + isUnsafeEval() { + return capnp_ts_1.Struct.getUint16(0, this) === 17; + } + setUnsafeEval() { + capnp_ts_1.Struct.setUint16(0, 17, this); + } toString() { return "Worker_Binding_" + super.toString(); } @@ -1762,6 +1770,7 @@ Worker_Binding.QUEUE = Worker_Binding_Which.QUEUE; Worker_Binding.FROM_ENVIRONMENT = Worker_Binding_Which.FROM_ENVIRONMENT; Worker_Binding.ANALYTICS_ENGINE = Worker_Binding_Which.ANALYTICS_ENGINE; Worker_Binding.HYPERDRIVE = Worker_Binding_Which.HYPERDRIVE; +Worker_Binding.UNSAFE_EVAL = Worker_Binding_Which.UNSAFE_EVAL; Worker_Binding.Type = Worker_Binding_Type; Worker_Binding.DurableObjectNamespaceDesignator = Worker_Binding_DurableObjectNamespaceDesignator; diff --git a/packages/miniflare/src/runtime/config/workerd.ts b/packages/miniflare/src/runtime/config/workerd.ts index 5c8835f66bbd..9a5f2d48b402 100644 --- a/packages/miniflare/src/runtime/config/workerd.ts +++ b/packages/miniflare/src/runtime/config/workerd.ts @@ -97,6 +97,7 @@ export type Worker_Binding = { | { fromEnvironment?: string } | { analyticsEngine?: ServiceDesignator } | { hyperdrive?: Worker_Binding_Hyperdrive } + | { unsafeEval?: Void } ); export interface Worker_Binding_Parameter { diff --git a/packages/miniflare/test/index.spec.ts b/packages/miniflare/test/index.spec.ts index 31e4426331b1..f01f23da165b 100644 --- a/packages/miniflare/test/index.spec.ts +++ b/packages/miniflare/test/index.spec.ts @@ -592,7 +592,7 @@ test("Miniflare: `node:`, `cloudflare:` and `workerd:` modules", async (t) => { script: ` import assert from "node:assert"; import { Buffer } from "node:buffer"; - import { connect } from "cloudflare:sockets"; + import { connect } from "cloudflare:sockets"; import rtti from "workerd:rtti"; export default { fetch() { @@ -1087,3 +1087,31 @@ unixSerialTest( t.is(await res.text(), "When I grow up, I want to be a big workerd!"); } ); + +test("Miniflare: allows the use of unsafe eval bindings", async (t) => { + const log = new TestLog(t); + + const mf = new Miniflare({ + log, + modules: true, + script: ` + export default { + fetch(req, env, ctx) { + const three = env.UNSAFE_EVAL.eval('2 + 1'); + + const fn = env.UNSAFE_EVAL.newFunction( + 'return \`the computed value is \${n}\`', '', 'n' + ); + + return new Response(fn(three)); + } + } + `, + unsafeEvalBinding: "UNSAFE_EVAL", + }); + t.teardown(() => mf.dispose()); + + const response = await mf.dispatchFetch("http://localhost"); + t.true(response.ok); + t.is(await response.text(), "the computed value is 3"); +});