Skip to content

fix(util): cross realm IsPlainObject check #4627

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

Merged
merged 4 commits into from
Jun 6, 2025

Conversation

gislerro
Copy link
Contributor

@gislerro gislerro commented Jun 5, 2025

I ran into an issue when creating zod schemas and passing them into a vm.Script which then performs validation on objects created in that script. Here's a test which illustrates the issue:

import vm from "node:vm";
import { z } from "zod/v4";

describe("Zod Issue", () => {
  it("should validate", () => {
    const MyKeys = z.literal(["key"]);
    const MySchema = z.record(MyKeys, z.string());

    const args = {
      key: "value",
    };

    // This works
    const data = MySchema.parse(args);
    console.log(data);

    // Running this in the VM context fails
    // since the `isPlainObject` check fails
    // the Object constructors are different between host and 'VM'
    // so a more robust `isPlainObject` implementation is needed
    const code = `
            const args = {
              key: "value"
            };

            const data = MySchema.parse(args);

            console.log(data);
        `;

    const context = vm.createContext({
      MySchema,
      console,
    });
    const script = new vm.Script(code);

    try {
      script.runInContext(context);
    } catch (error) {
      if (error instanceof z.ZodError) {
        console.error("Error running script:", error.issues);
      } else {
        throw error;
      }
    }
  });
});

which outputs:

host { key: 'value' }
Error running script: [
  {
    expected: 'record',
    code: 'invalid_type',
    path: [],
    message: 'Invalid input: expected record, received Object'
  }
]

The issue stems from the fact that inside the context there's a different Object constructor hence isPlainObject fails which produces the error. Ive adapted the isPlainObject to include additional checks if the previous one hasn't been conclusive. The fix is inspired by: https://www.npmjs.com/package/is-plain-object

I've kept the fast path the same as in the original implementation

@colinhacks colinhacks merged commit 8ab2374 into colinhacks:main Jun 6, 2025
3 of 4 checks passed
@colinhacks
Copy link
Owner

Good fix, thanks. This isn't used in regular z.object() so the fast path isn't a huge deal (and probably wasn't even faster tbh with the redundant Object.getPrototypeOf() calls). Thanks!

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.

2 participants