Skip to content

Conversation

@shkumbinhasani
Copy link

Summary

This PR fixes a critical security vulnerability where body validation could be bypassed by omitting the content-type header. The vulnerability was introduced in v0.15.2 (PR #686).

Problem

As reported in issue #891, starting from @hono/zod-openapi v0.15.2, request body validation can be bypassed by simply omitting the content-type header. This allows attackers to invoke handlers with unvalidated payloads, potentially leading to:

  • Type safety violations
  • Unexpected behavior in business logic
  • Security vulnerabilities (SQL injection, privilege escalation, etc.)

Solution

This PR makes validation strict by default to prevent the bypass:

  1. Default behavior (when required is not specified): Always validate the request body
  2. When required: true: Always validate the request body
  3. When required: false: Allow empty body only when no content-type is provided

Key Changes

  • Modified validation logic to be secure by default
  • Return 400 error for mismatched content-types (except when multiple content-types are supported)
  • Added comprehensive security tests to prevent regression
  • Updated existing tests to reflect the new secure behavior

Breaking Change

⚠️ BREAKING: Requests without content-type are now validated by default instead of bypassing validation.

To maintain backward compatibility for legitimate use cases, explicitly set required: false in the route configuration to allow optional body validation.

Test Plan

  • Added comprehensive security tests (validation-security.test.ts)
  • All existing tests pass after updates
  • Verified validation cannot be bypassed by omitting content-type
  • Verified multiple content-type scenarios work correctly

Related Issues

Fixes #891

This fixes a critical security issue where body validation could be bypassed by omitting the content-type header. The vulnerability was introduced in v0.15.2.

Changes:
- Made validation strict by default (when `required` is not specified)
- Only allow empty body when `required` is explicitly set to `false` AND no content-type is provided
- Return 400 error for mismatched content-types in single content-type scenarios
- Skip validators gracefully when multiple content-types are supported
- Added comprehensive security tests to prevent regression

BREAKING CHANGE: Requests without content-type are now validated by default instead of bypassing validation. To allow optional body, explicitly set `required: false` in the route configuration.

Fixes the security vulnerability reported where attackers could bypass validation by omitting the content-type header, potentially leading to unexpected behavior in handlers that expect validated payloads.
@changeset-bot
Copy link

changeset-bot bot commented Aug 11, 2025

🦋 Changeset detected

Latest commit: 2a57b5a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@hono/zod-openapi Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codecov
Copy link

codecov bot commented Aug 11, 2025

Codecov Report

❌ Patch coverage is 88.63636% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 4.35%. Comparing base (86a4ca4) to head (d903d49).
⚠️ Report is 123 commits behind head on main.

Files with missing lines Patch % Lines
packages/zod-openapi/src/index.ts 88.63% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #1376       +/-   ##
==========================================
- Coverage   79.32%   4.35%   -74.98%     
==========================================
  Files          81     103       +22     
  Lines        2443    3402      +959     
  Branches      633     877      +244     
==========================================
- Hits         1938     148     -1790     
- Misses        419    2648     +2229     
- Partials       86     606      +520     
Flag Coverage Δ
ajv-validator ?
arktype-validator ?
auth-js ?
bun-compress ?
casbin ?
class-validator ?
clerk-auth ?
cloudflare-access ?
conform-validator ?
effect-validator ?
esbuild-transpiler ?
event-emitter ?
firebase-auth ?
graphql-server ?
hello ?
medley-router ?
node-ws ?
oauth-providers ?
oidc-auth ?
otel ?
prometheus ?
react-renderer ?
sentry ?
standard-validator ?
swagger-editor ?
swagger-ui ?
trpc-server ?
tsyringe ?
typebox-validator ?
typia-validator ?
valibot-validator ?
zod-openapi 4.35% <88.63%> (-0.68%) ⬇️
zod-validator ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@yusukebe
Copy link
Member

@shkumbinhasani

Thanks for the PR. I started considering it.

Please ignore the CI error.

Copy link
Member

@yusukebe yusukebe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do the two things:

  • Add a changeset with yarn changeset command on the top of the project. This is a patch change.
  • Update the README for this change.

@yusukebe
Copy link
Member

Hi @shkumbinhasani

Thank you for the PR! I considered it, but you're absolutely right. This should be fixed right now. I've left comments; please check them.

- Add multiple content-type support scenarios tests
- Fix validation logic for multiple content types
- Add final check to ensure validation runs for unsupported content-types
- Update README with detailed body validation behavior documentation
- Add changeset for the security patch

This ensures requests with unsupported content-types or missing content-types
are properly validated when multiple content types are supported.
@shkumbinhasani
Copy link
Author

Hi @yusukebe Thanks for the review!

  • Added the requested changeset (patch) via yarn changeset
  • Updated the README to document the new strict validation behavior
  • Verified all tests pass and security tests still cover the bypass case

Copy link
Member

@yusukebe yusukebe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to consider what should happen with non-JSON/Form content types. Currently, the behavior is inconsistent.

For example, consider this test case:

describe('Non-JSON/Form content-types', () => {
  const route = createRoute({
    method: 'post',
    path: '/text',
    request: {
      body: {
        content: {
          'text/plain': {
            schema: z.string().max(1),
          },
        },
      },
    },
    responses: {
      200: {
        description: 'Success',
        content: {
          'application/json': {
            schema: z.object({ success: z.boolean() }),
          },
        },
      },
    },
  })

  const app = new OpenAPIHono()
  app.openapi(route, async (c) => {
    return c.json({ success: true })
  })

  it('should validate text/plain content and reject invalid data', async () => {
    const res = await app.request('/text', {
      method: 'POST',
      body: 'body text',
      headers: { 'Content-Type': 'text/plain' },
    })
    expect(res.status).toBe(400)
  })
})

Currently, this test fails - it returns 200 instead of 400 because text/plain validation is not implemented.

So, we need to decide:

  1. Should we validate text/plain (or application/xml etc.) against the schema?
  2. Should we pass it through without validation?
  3. Should we return an error for non-JSON/Form content types?

I think we don't need implement all things in this PR, but we have to decide the direction.

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.

[zod-openapi][security] Unexpected validation bypass when omitting content-type

2 participants