Zod Framework Integrations — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Zod Framework Integrations (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
The fastify-type-provider-zod package connects Zod schemas directly to Fastify's type inference system:
import Fastify from "fastify";
import { ZodTypeProvider, serializerCompiler, validatorCompiler } from "fastify-type-provider-zod";
import { z } from "zod";
const app = Fastify().withTypeProvider<ZodTypeProvider>();
// Required: attach Zod compilers
app.setValidatorCompiler(validatorCompiler);
app.setSerializerCompiler(serializerCompiler);
const CreateUserBody = z.strictObject({
name: z.string().min(1),
email: z.email(),
});
const UserResponse = z.object({
id: z.string().uuid(),
name: z.string(),
email: z.email(),
});
app.post("/users", {
schema: {
body: CreateUserBody,
response: { 201: UserResponse },
},
}, async (req, reply) => {
// req.body is fully typed as z.infer<typeof CreateUserBody>
// Response schema prevents leaking fields not in UserResponse
const user = await db.createUser(req.body);
return reply.status(201).send(user);
});Key benefits:
Security note: Use z.strictObject() for request body schemas to prevent over-posting. The response schema can use z.object() (strip mode) since you're controlling what goes out.
Use .safeParse() inside Server Actions — never .parse(). A thrown exception in a Server Action crashes the action instead of returning a validation error to the client:
// shared/schemas.ts — shared between client and server
export const CreatePostSchema = z.object({
title: z.string().min(1).max(100),
content: z.string().min(10),
});
export type CreatePostInput = z.infer<typeof CreatePostSchema>;
// app/actions/posts.ts
"use server";
import { CreatePostSchema } from "@/shared/schemas";
export async function createPost(formData: FormData) {
const raw = {
title: formData.get("title"),
content: formData.get("content"),
};
const result = CreatePostSchema.safeParse(raw);
if (!result.success) {
return {
success: false,
errors: z.flattenError(result.error).fieldErrors,
};
}
// result.data is fully typed
await db.posts.create(result.data);
return { success: true };
}zodResolverimport { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
// Form schemas often need coercion for HTML input types
const RegisterSchema = z.object({
email: z.email(),
age: z.coerce.number().int().min(13).max(120), // HTML inputs return strings
birthdate: z.coerce.date(), // <input type="date"> returns string
avatar: z.file().max(5 * 1024 * 1024).type("image/*"),
});
type RegisterForm = z.infer<typeof RegisterSchema>;
function RegisterForm() {
const { register, handleSubmit, formState: { errors } } =
useForm<RegisterForm>({ resolver: zodResolver(RegisterSchema) });
return (
<form onSubmit={handleSubmit(async (data) => { /* data is typed RegisterForm */ })}>
<input {...register("email")} />
{errors.email && <span>{errors.email.message}</span>}
</form>
);
}Form schema vs API schema: Form schemas often differ from API schemas due to coercion needs (z.coerce.number(), z.coerce.date()). Share a base schema and layer coercions on top for the form layer rather than duplicating the entire schema.
Multi-step forms: Wrap with FormProvider and use useFormContext() in step components — avoid prop drilling the register/errors objects.
The canonical architecture for full-stack type safety:
packages/
schemas/ ← Shared Zod schemas
package.json ← "zod" as peerDependency (NOT dependency)
src/
user.ts
post.ts
index.ts ← Re-export all schemas
apps/
web/ ← Frontend (imports from @workspace/schemas)
api/ ← Backend (imports from @workspace/schemas)// packages/schemas/src/user.ts
import { z } from "zod"; // Resolved to the single workspace Zod instance
export const UserSchema = z.object({
id: z.string().uuid(),
email: z.email(),
role: z.enum(["admin", "user"]),
});
export type User = z.infer<typeof UserSchema>;
// apps/web/src/auth.tsx
import { UserSchema } from "@workspace/schemas"; // ✅ Same Zod instance
// NOT: import { z } from "zod" // ❌ May create a second instanceForce single Zod version at workspace root:
// package.json (workspace root)
{
"pnpm": {
"overrides": { "zod": "^4.0.0" }
},
// npm: "overrides": { "zod": "^4.0.0" }
// yarn: "resolutions": { "zod": "^4.0.0" }
}This error occurs in Zod 4.1.13+ when the same schema with .meta({ id }) is evaluated twice — either from duplicate Zod instances or from schema definitions inside dynamic/called functions:
// WRONG: Dynamic schema creation generates duplicate IDs
function buildSchema(fields: string[]) {
return z.object({ name: z.string() }).meta({ id: "user-schema" }); // Re-registered on each call!
}
// CORRECT: Static, module-level schema with .meta() id
export const UserSchema = z.object({ name: z.string() }).meta({ id: "user-schema" });Resolution steps:
"zod" is a peerDependency in shared packages (not dependency).meta({ id }) assignments to module-level constantszodSchema() HelperWhen using Zod schemas with the Vercel AI SDK for structured output or tool definitions:
import { generateObject } from "ai";
import { zodSchema } from "ai/zod";
import { z } from "zod";
const RecipeSchema = z.object({
name: z.string(),
ingredients: z.array(z.object({ item: z.string(), amount: z.string() })),
steps: z.array(z.string()),
});
const { object } = await generateObject({
model: openai("gpt-4o"),
schema: zodSchema(RecipeSchema),
prompt: "Generate a recipe for chocolate cake",
});
// object is typed as z.infer<typeof RecipeSchema>~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.