diff --git a/.changeset/silent-worms-mate.md b/.changeset/silent-worms-mate.md new file mode 100644 index 00000000..c9aba3c2 --- /dev/null +++ b/.changeset/silent-worms-mate.md @@ -0,0 +1,5 @@ +--- +'@hono/arktype-validator': patch +--- + +Don't return restricted data fields on error responses diff --git a/packages/arktype-validator/src/index.test.ts b/packages/arktype-validator/src/index.test.ts index 11dfa12b..b77d5a35 100644 --- a/packages/arktype-validator/src/index.test.ts +++ b/packages/arktype-validator/src/index.test.ts @@ -35,6 +35,17 @@ describe('Basic', () => { } ) + app.get( + '/headers', + arktypeValidator( + 'header', + type({ + 'User-Agent': 'string', + }) + ), + (c) => c.json({ success: true, userAgent: c.header('User-Agent') }) + ) + type Actual = ExtractSchema type Expected = { '/author': { @@ -98,6 +109,22 @@ describe('Basic', () => { const data = (await res.json()) as { success: boolean } expect(data['success']).toBe(false) }) + + it("doesn't return cookies after headers validation", async () => { + const req = new Request('http://localhost/headers', { + headers: { + 'User-Agent': 'invalid', + Cookie: 'SECRET=123', + }, + }) + + const res = await app.request(req) + expect(res).not.toBeNull() + expect(res.status).toBe(400) + const data = (await res.json()) as { succcess: false; errors: type.errors } + expect(data.errors).toHaveLength(1) + expect(data.errors[0].data).not.toHaveProperty('cookie') + }) }) describe('With Hook', () => { diff --git a/packages/arktype-validator/src/index.ts b/packages/arktype-validator/src/index.ts index 4dbdde8d..d3254902 100644 --- a/packages/arktype-validator/src/index.ts +++ b/packages/arktype-validator/src/index.ts @@ -10,6 +10,10 @@ export type Hook = ( type HasUndefined = undefined extends T ? true : false +const RESTRICTED_DATA_FIELDS = { + header: ['cookie'], +} + export const arktypeValidator = < T extends Type, Target extends keyof ValidationTargets, @@ -54,7 +58,31 @@ export const arktypeValidator = < return c.json( { success: false, - errors: out, + errors: + target in RESTRICTED_DATA_FIELDS + ? out.map((error) => { + const restrictedFields = + RESTRICTED_DATA_FIELDS[target as keyof typeof RESTRICTED_DATA_FIELDS] || [] + + if ( + error && + typeof error === 'object' && + 'data' in error && + typeof error.data === 'object' && + error.data !== null && + !Array.isArray(error.data) + ) { + const dataCopy = { ...(error.data as Record) } + for (const field of restrictedFields) { + delete dataCopy[field] + } + + error.data = dataCopy + } + + return error + }) + : out, }, 400 )