Skip to content

Commit

Permalink
Add support for unsafe eval bindings to miniflare (#4322)
Browse files Browse the repository at this point in the history
* update workerd config schema
* add unsafeEvalBinding to miniflare core options
* add unsafeEval test
* add changeset
  • Loading branch information
dario-piotrowicz committed Nov 2, 2023
1 parent 29a59d4 commit 8a25b7f
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 1 deletion.
30 changes: 30 additions & 0 deletions .changeset/tricky-otters-rescue.md
Original file line number Diff line number Diff line change
@@ -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",
});
```
9 changes: 9 additions & 0 deletions packages/miniflare/src/plugins/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions packages/miniflare/src/runtime/config/workerd.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
4 changes: 4 additions & 0 deletions packages/miniflare/src/runtime/config/workerd.capnp.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
9 changes: 9 additions & 0 deletions packages/miniflare/src/runtime/config/workerd.capnp.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}))
Expand Down Expand Up @@ -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();
}
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/miniflare/src/runtime/config/workerd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export type Worker_Binding = {
| { fromEnvironment?: string }
| { analyticsEngine?: ServiceDesignator }
| { hyperdrive?: Worker_Binding_Hyperdrive }
| { unsafeEval?: Void }
);

export interface Worker_Binding_Parameter {
Expand Down
30 changes: 29 additions & 1 deletion packages/miniflare/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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");
});

0 comments on commit 8a25b7f

Please sign in to comment.