Files
orca/mobile/scripts/zod-security.test.ts
T

107 lines
3.9 KiB
TypeScript

import { createRequire } from 'node:module'
import { dirname, join } from 'node:path'
import { pathToFileURL } from 'node:url'
import { describe, expect, it } from 'vitest'
const require = createRequire(import.meta.url)
const expoRequire = createRequire(require.resolve('expo'))
const cliRequire = createRequire(expoRequire.resolve('@expo/cli'))
const zodRoot = dirname(cliRequire.resolve('zod/package.json'))
describe.each(['cjs', 'js'])('installed Expo Zod (%s)', (extension) => {
it.each(['index', 'v3/index', 'v4/index'])(
'rejects oversized collections before parsing elements in %s',
async (entry) => {
const path = join(zodRoot, `${entry}.${extension}`)
const z = extension === 'cjs' ? require(path) : await import(pathToFileURL(path).href)
let parsed = 0
let refined = 0
const element = z.preprocess((value: unknown) => {
parsed += 1
return value
}, z.string())
const cases = [
{
schema: z.array(element).max(2, 'too many'),
oversized: Array(1_000).fill(0),
valid: ['a', 'b']
},
{
schema: z.array(element).length(2, 'too many'),
oversized: Array(1_000).fill(0),
valid: ['a', 'b']
},
{
schema: z.set(element).max(2, 'too many'),
oversized: new Set(Array.from({ length: 1_000 }, (_, i) => i)),
valid: new Set(['a', 'b'])
},
{
schema: z.set(element).size(2, 'too many'),
oversized: new Set(Array.from({ length: 1_000 }, (_, i) => i)),
valid: new Set(['a', 'b'])
}
]
for (const { schema, oversized, valid } of cases) {
const parent = z.object({ items: schema }).refine(() => {
refined += 1
return true
})
parsed = 0
refined = 0
for (const result of [
parent.safeParse({ items: oversized }),
await parent.safeParseAsync({ items: oversized })
]) {
expect(result.success).toBe(false)
expect(result.error.issues).toEqual([
expect.objectContaining({ code: 'too_big', path: ['items'], message: 'too many' })
])
}
expect([parsed, refined]).toEqual([0, 0])
expect(parent.parse({ items: valid })).toEqual({ items: valid })
expect(await parent.parseAsync({ items: valid })).toEqual({ items: valid })
expect(parsed).toBe(4)
}
expect(z.array(z.string()).max(2).safeParse([1]).success).toBe(false)
expect(z.array(z.string()).length(2).safeParse(['a']).success).toBe(false)
expect(
z
.set(z.string())
.size(2)
.safeParse(new Set(['a'])).success
).toBe(false)
if (entry === 'v4/index') {
expect(
z
.array(z.number())
.overwrite((items: number[]) => items.slice(0, 2))
.max(2)
.parse([1, 2, 3])
).toEqual([1, 2])
}
}
)
it('protects the bundled Zod mini entrypoint', async () => {
const path = join(zodRoot, `v4/mini/index.${extension}`)
const z = extension === 'cjs' ? require(path) : await import(pathToFileURL(path).href)
const schema = z.array(z.string()).check(z.maxLength(2))
expect(z.safeParse(schema, [1, 2, 3]).error.issues).toEqual([
expect.objectContaining({ code: 'too_big' })
])
expect((await z.safeParseAsync(schema, [1, 2, 3])).error.issues).toHaveLength(1)
expect(z.parse(schema, ['a'])).toEqual(['a'])
const map = z.map(z.string(), z.string()).check(z.maxSize(1))
const oversized = new Map([
[1, 1],
[2, 2]
])
expect(z.safeParse(map, oversized).error.issues).toEqual([
expect.objectContaining({ code: 'too_big' })
])
expect((await z.safeParseAsync(map, oversized)).error.issues).toHaveLength(1)
expect(z.parse(map, new Map([['a', 'b']]))).toEqual(new Map([['a', 'b']]))
})
})