-
Notifications
You must be signed in to change notification settings - Fork 252
Description
Hey there,
starting from "@hono/zod-openapi": "0.15.2"
the body validation is skipped if the content-type is not matched - ref PR.
Personally I was very surprised by this as this means you can effectively bypass validations and invoke the handler as long as you just omit the content-type.
I was also not able to find any mentioning of this in the docs. I think this can be a significant security risk as it causes unexpected behaviour in the handler which might expect a validated payload.
I would argue that the mention PR should be reverted and the handler should fail if the content-type is not matched, but at least this should be clearly documented and have been marked as a breaking change.
Reproduction
import { createRoute, z, OpenAPIHono } from '@hono/zod-openapi';
const route = createRoute({
method: 'POST',
path: '/book',
request: {
// This would mitigate the issue as we enforce the usage of `application/json` content-type
//headers: z.object({
// 'content-type': z.literal('application/json'),
//}),
body: {
content: {
'application/json': {
schema: z.object({
title: z.string().startsWith('[TITLE]').min(10).max(30),
}),
},
},
},
},
});
const app = new OpenAPIHono();
app.openapi(route, (c) => {
const validatedBody = c.req.valid('json');
console.log('This will only be logged when the request body passed validation, right?!', validatedBody);
return c.json({ processed: true });
});
console.log("Case I: sending a valid request with content-type `application/json`")
/**
* Output:
*
* This will only be logged when the request body passed validation, right?! { title: '[TITLE] Hello World' }
* { status: 200, response: { processed: true } }
*/
await app.request('/book', {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify({ title: '[TITLE] Hello World' }),
}).then(async response => console.log({ status: response.status, response: await response.json() }));
console.log("\n\nCase II: sending a malformed request with content-type `application/json`")
/**
* Output:
*
* {
* status: 400,
* response: { success: false, error: { issues: [Array], name: 'ZodError' } }
* }
*/
await app.request('/book', {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify({ title: 'not valid' }),
}).then(async response => console.log({ status: response.status, response: await response.json() }));
console.log("\n\nCase III: sending a malformed request without any content-type")
/**
* Output:
*
* Case III: sending a malformed request without any content-type
* This will only be logged when the request body passed validation, right?! {}
* { status: 200, response: { processed: true } }
*/
await app.request('/book', {
method: 'POST',
body: JSON.stringify({ title: 'not valid' }),
}).then(async response => console.log({ status: response.status, response: await response.json() }));
With "@hono/zod-openapi": "0.15.1"
you got the following behaviour - which I would expect:
Case I: sending a valid request with content-type `application/json`
This will only be logged when the request body passed validation, right?! { title: '[TITLE] Hello World' }
{ status: 200, response: { processed: true } }
Case II: sending a malformed request with content-type `application/json`
{
status: 400,
response: { success: false, error: { issues: [Array], name: 'ZodError' } }
}
Case III: sending a malformed request without any content-type
{
status: 400,
response: { success: false, error: { issues: [Array], name: 'ZodError' } }
}
starting from "@hono/zod-openapi": "0.15.2"
the handler is invoked if the content-type is omitted:
Case I: sending a valid request with content-type `application/json`
This will only be logged when the request body passed validation, right?! { title: '[TITLE] Hello World' }
{ status: 200, response: { processed: true } }
Case II: sending a malformed request with content-type `application/json`
{
status: 400,
response: { success: false, error: { issues: [Array], name: 'ZodError' } }
}
// ‼️ This is the problem ‼️
Case III: sending a malformed request without any content-type
This will only be logged when the request body passed validation, right?! {}
{ status: 200, response: { processed: true } }
as a mitigation the content-type can be enforced by validating the incoming content-type
header.
Thanks!