Skip to content

🎯 Restore Request Body Stream After Context.Bind() to Prevent Downstream Body Exhaustion #1

Description

@RoseMark45

📝 Description

When Context.Bind() (or a custom binder) is invoked in early middleware (such as an authentication or validation layer), the HTTP request body stream (c.Request().Body) is fully consumed. Because Go's io.ReadCloser can only be read once, any downstream middleware or handlers—such as request auditors, HMAC verifiers, or structured loggers—that attempt to read the request body subsequently receive an empty stream (EOF).

Unlike some frameworks that provide built-in options to cache or duplicate the request body stream, Echo permanently replaces the stream with an exhausted reader. This issue affects all payload types, including JSON, XML, and form-encoded data.

🎯 Acceptance Criteria

  • Implement a mechanism (e.g., a reusable middleware or an extension to the binding process) that allows the request body to be read multiple times.
  • Ensure that after Context.Bind() is called, downstream middleware can still read the full, original request body from c.Request().Body.
  • The solution must support JSON, XML, and form-encoded payloads.
  • The solution must handle stream restoration safely without causing memory leaks or panic conditions on empty/nil request bodies.
  • Provide a configuration or opt-in flag to limit the maximum buffered body size to prevent Denial of Service (DoS) attacks via memory exhaustion.

🛠️ Technical Specifications & Context

In Go's net/http package, Request.Body is an io.ReadCloser. Once read, it cannot be read again unless explicitly replaced.

Proposed Implementation Strategy:

  1. Request Body Caching Middleware:
    Create a middleware (e.g., middleware.BodyCache() or similar) that runs early in the chain. This middleware should:

    • Read the request body into memory (up to a configurable limit).
    • Replace c.Request().Body with a new io.ReadCloser using io.NopCloser(bytes.NewReader(bodyBytes)).
    • Ensure that subsequent reads also reset or replace the body, or store the raw bytes in the Echo Context (using c.Set("rawBody", bodyBytes)) so downstream components can access it without re-reading the stream.
  2. Alternative: Custom Binder Wrapper:
    If modifying the middleware chain is not preferred, override the default Echo Binder (e.Binder) to intercept the binding process, read the body, bind the data, and then restore c.Request().Body using:

    bodyBytes, _ := io.ReadAll(c.Request().Body)
    c.Request().Body = io.NopCloser(bytes.NewBuffer(bodyBytes))

Key Files to Modify/Create:

  • middleware/body_cache.go (or similar middleware path in the RoseMark45/echo repository structure).
  • binder.go (if modifying the default binding behavior).

🧪 Verification & Testing

To verify the fix, Hunter should implement the following tests:

  1. Integration Test - Middleware Chain Execution:

    • Create an Echo instance.
    • Register an early middleware that calls c.Bind(&struct{...}).
    • Register a downstream middleware that reads c.Request().Body using io.ReadAll.
    • Assert that the downstream middleware successfully reads the exact payload sent in the request.
  2. Payload Type Coverage:

    • Test with a JSON payload: {"foo": "bar"}.
    • Test with an XML payload: <xml><foo>bar</foo></xml>.
    • Test with form-encoded data: foo=bar.
  3. Edge Case Tests:

    • Test with an empty request body (should not panic or error).
    • Test with a request body exceeding the configured maximum buffer limit (should return an appropriate HTTP 413 Payload Too Large status).

Opire Bounty


This repo is using Opire - what does it mean? 👇
💵 Everyone can add rewards for this issue commenting /reward 100 (replace 100 with the amount).
🕵️‍♂️ If someone starts working on this issue to earn the rewards, they can comment /try to let everyone know!
🙌 And when they open the PR, they can comment /claim #1 either in the PR description or in a PR's comment.

🪙 Also, everyone can tip any user commenting /tip 20 @RoseMark45 (replace 20 with the amount, and @RoseMark45 with the user to tip).

📖 If you want to learn more, check out our documentation.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions