Skip to content

feat(server): configurable body limit + group-404 guard hardening for echo v5.3.0 - #711

Merged
gaborage merged 3 commits into
mainfrom
fix/echo-530-group404-guard
Jul 15, 2026
Merged

feat(server): configurable body limit + group-404 guard hardening for echo v5.3.0#711
gaborage merged 3 commits into
mainfrom
fix/echo-530-group404-guard

Conversation

@gaborage

@gaborage gaborage commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Summary

Supersedes #692 (the Renovate echo/v5 v5.2.1 → v5.3.0 bump). Multi-lens analysis showed that bump is behavior-affecting, not a routine version bump — the existing test suite is green but structurally blind to what v5.3.0 changes. This PR carries the bump (plus its OTel companion echo-opentelemetry v0.0.2 → v0.0.3) and the hardening + tests + docs that make the behavior changes safe, reviewed, and pinned.

What v5.3.0 changes (and how this PR handles it)

1. Group implicit-404 revert (the headline). echo restored v4 behavior: a middleware-bearing group auto-registers an implicit /* catch-all. Verified reachable in go-bricks via the scheduler /_sys CIDR gate and the debug auth group. Two effects:

  • Security win (kept deliberately): those gates now also run on unmatched sub-paths and wrong-method requests under their prefix — no 404-vs-403 existence oracle, gate not bypassable via a bogus sub-path. So we do not set NoGroupAutoRegister404Routes.
  • 🔧 Latent contract break (fixed): a group catch-all has RouteInfo().Name == "" (not the sentinel) with Method == echo.RouteNotFound, which defeated HandlerContext.PathParams()/RouteTemplate()'s name-only guard (would surface a phantom * param / /group/* template). The guard now keys on Method == echo.RouteNotFound. A wrong-method request under such a group now returns 404, not 405 (the catch-all shadows echo's automatic 405); top-level routes are unaffected.

2. Stricter JSON bind. echo's Deserialize switched from a streaming json.Decoder to json.Unmarshal over a pooled buffer (a small per-bind allocation win). Trailing non-whitespace bytes after the top-level JSON value are now rejected with 400 (v5.2.1 silently accepted them); trailing whitespace still binds. Both sides pinned by a test.

3. New server.bodylimit config (int64 bytes, default 10 MB, env SERVER_BODYLIMIT) makes the request body cap configurable. A negative value is rejected at config validation (mirroring server.gzip.minlength); a <=0 value resolves to the 10 MB default at wire-up (defense-in-depth for direct SetupMiddlewares callers) — the cap can never be silently disabled.

Also: statusToErrorCode now maps 405 → METHOD_NOT_ALLOWED (was INTERNAL_ERROR); a Renovate packageRule groups echo/v5 with echo-opentelemetry so the engine and its instrumentation always update together.

Tests & docs

New/expanded tests: group + nested-group catch-all guard (with mutation-checked assertions), global-404/405 RouteTemplate asymmetry, JSON trailing-content boundary, body-limit enforcement + non-positive fallback (0 and negative), 405→METHOD_NOT_ALLOWED end-to-end, and config default/validation. Docs: migrations.md E51 hop (adopt-only), observability.md (group-404 http.route label shift), startup_defaults.md (server.bodylimit).

No exported go-bricks signature changes; additive/adopt-only for consumers.

Note on versioning

The migrations.md E51 hop assumes the release cuts as v0.51.0 (this carries observable behavior changes → minor bump). If release-please lands a different version, the hop's version labels need a one-line reconciliation.

Verification

make check green (fmt + lint/gosec + -race tests + alloc guards + govulncheck 0 vulns). Pre-push gates run in order: /simplify/security-audit/code-review (CodeRabbit, converged to 0 findings). The design and the guard-fix predicate were established by direct empirical probing of echo v5.3.0, and the implementation was adversarially challenged (mutation checks confirm each fix's test fails without it; a completeness sweep confirmed no sibling site shares the guard's blind spot).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added server.bodylimit to configure the maximum request body size (default 10 MB); 0 uses the default, and negative values are rejected.
    • Middleware now enforces the configured body limit.
  • Bug Fixes
    • Improved unmatched routing behavior: unmatched requests under implicit group catch-alls now return empty route/template and no path params.
    • JSON binding now rejects trailing non-whitespace bytes.
    • HTTP 405 responses now map to METHOD_NOT_ALLOWED.
  • Documentation
    • Updated startup defaults, migration guidance, and observability notes for routing, binding, and body limit behavior.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 43 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4802772a-dab8-419d-9380-9a5940d9d32c

📥 Commits

Reviewing files that changed from the base of the PR and between 83f2449 and 5abb743.

📒 Files selected for processing (4)
  • config/config_test.go
  • wiki/migrations.md
  • wiki/observability.md
  • wiki/startup_defaults.md

Walkthrough

Echo is upgraded to v5.3.0. Routing metadata, 405 error mapping, JSON binding, and request body limits are updated, with configuration validation, middleware tests, migration guidance, and observability documentation.

Changes

Echo routing and binding behavior

Layer / File(s) Summary
Echo routing, binding, and error responses
go.mod, renovate.json, server/constants.go, server/handler.go, server/handler_test.go, server/server.go, server/server_test.go
Echo v5.3.0 behavior is integrated and tested for group catch-all routes, unmatched route metadata, trailing JSON content, and explicit METHOD_NOT_ALLOWED responses.

Configurable request body limit

Layer / File(s) Summary
Body-limit configuration and enforcement
config.example.yaml, config/types.go, config/validation.go, config/config.go, config/config_test.go, config/validation_test.go, server/middleware.go, server/middleware_test.go, wiki/startup_defaults.md
server.bodylimit is added with a 10 MiB default, negative-value validation, middleware wiring, documentation, and tests for configured and fallback limits.

Upgrade and documentation guidance

Layer / File(s) Summary
Migration and observability guidance
wiki/migrations.md, wiki/observability.md
Documentation describes Echo v5.3.0 routing, JSON binding, and route-label changes, plus migration checks for request body limits.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant EchoRouter
  participant HandlerContext
  participant ErrorEnvelope
  Client->>EchoRouter: request unmatched path or method
  EchoRouter->>HandlerContext: classify route metadata and parameters
  HandlerContext-->>EchoRouter: empty or preserved route context
  EchoRouter->>ErrorEnvelope: map 405 to METHOD_NOT_ALLOWED
  ErrorEnvelope-->>Client: return JSON error envelope
Loading

Possibly related PRs

Suggested labels: enhancement, documentation

Poem

I’m a bunny guarding bytes at the door,
Echo hops through routes as before.
405s now wear a name,
JSON trails join the game.
Docs bloom bright—春! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: configurable server body limits and hardened unmatched-group handling for the Echo v5.3.0 update.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/echo-530-group404-guard

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@wiki/migrations.md`:
- Line 608: Update the migration detection command to search all tracked
configuration sources for SERVER_BODYLIMIT, including nested paths and both
.yaml and .yml files, while retaining the existing bodylimit pattern matching.
Ensure the no-match gate is based on the complete tracked configuration set
rather than only root-level config*.yaml files.

In `@wiki/observability.md`:
- Line 13: The documentation in “Group-Scoped 404 Route Labels (echo v5.3.0)”
incorrectly implies wrong-method spans are always named GET. Update the
span-name example to use a method placeholder, or explicitly state GET is only
an example, while preserving the route-label and metric guidance.

In `@wiki/startup_defaults.md`:
- Around line 38-40: Update the “Server Request Body Limit” documentation to
distinguish known Content-Length requests, which are rejected before the
handler, from chunked or unknown-length requests, which may exceed the cap while
being read by the handler. Preserve the 413 behavior and server.bodylimit
configuration details.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a7ae1c23-36ea-490c-a8af-45a5674f72f6

📥 Commits

Reviewing files that changed from the base of the PR and between 2ebd6ee and 4272af6.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (18)
  • config.example.yaml
  • config/config.go
  • config/config_test.go
  • config/types.go
  • config/validation.go
  • config/validation_test.go
  • go.mod
  • renovate.json
  • server/constants.go
  • server/handler.go
  • server/handler_test.go
  • server/middleware.go
  • server/middleware_test.go
  • server/server.go
  • server/server_test.go
  • wiki/migrations.md
  • wiki/observability.md
  • wiki/startup_defaults.md

Comment thread wiki/migrations.md Outdated
Comment thread wiki/observability.md Outdated
Comment thread wiki/startup_defaults.md Outdated
@gaborage
gaborage force-pushed the fix/echo-530-group404-guard branch from 4272af6 to 1469675 Compare July 15, 2026 15:10
… echo v5.3.0

Bumps github.com/labstack/echo/v5 v5.2.1 -> v5.3.0 (supersedes #692) and its
OTel companion echo-opentelemetry v0.0.2 -> v0.0.3, then hardens go-bricks
against v5.3.0's behavior changes:

- echo v5.3.0 restored v4 behavior where a middleware-bearing group
  auto-registers an implicit "/*" RouteNotFound catch-all. go-bricks KEEPS the
  new default (the /_sys CIDR gate and debug auth gate now cover unmatched
  sub-paths — a defense-in-depth win) and hardens HandlerContext.PathParams()
  and RouteTemplate() to still report "unmatched" for the catch-all: the guard
  now keys on RouteInfo().Method == echo.RouteNotFound (empty Name), not the
  Name sentinel alone.
- server.bodylimit (int64 bytes, default 10 MB, env SERVER_BODYLIMIT) makes the
  request body cap configurable; a non-positive value falls back to the default
  so it can't silently disable the limit.
- statusToErrorCode now maps 405 -> METHOD_NOT_ALLOWED (was INTERNAL_ERROR).
- Renovate packageRule groups echo/v5 with echo-opentelemetry so the engine and
  its instrumentation always update together.
- Docs: migrations.md E51 hop (adopt-only), observability.md route-label note,
  startup_defaults.md body-limit section.

No exported go-bricks signature changes; JSON bind is now stricter (trailing
bytes after the top-level value are rejected) via echo's pooled Deserialize.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gaborage
gaborage force-pushed the fix/echo-530-group404-guard branch from 1469675 to 83f2449 Compare July 15, 2026 15:16

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
wiki/startup_defaults.md (1)

40-40: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Qualify the request-body limit enforcement timing.

Known Content-Length requests above the cap can be rejected before handler execution, but chunked or unknown-length bodies may exceed the cap while the handler reads them. Update both descriptions to reflect this distinction.

  • wiki/startup_defaults.md#L40-L40: qualify the 413/pre-handler statement.
  • wiki/migrations.md#L638-L638: qualify the default and configured-limit verification guidance.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wiki/startup_defaults.md` at line 40, Qualify the request-body limit
documentation in wiki/startup_defaults.md at line 40 to state that known
Content-Length requests over the cap may be rejected with 413 before handler
execution, while chunked or unknown-length bodies can exceed the limit as the
handler reads them. Apply the same distinction to the default and
configured-limit verification guidance in wiki/migrations.md at line 638.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@config/config_test.go`:
- Line 54: Update clearEnvironmentVariables in config/config_test.go to unset
SERVER_BODYLIMIT before the test calls Load(), ensuring the default
cfg.Server.BodyLimit assertion is isolated from the external environment.

In `@wiki/migrations.md`:
- Around line 627-630: Update the C51.2 migration note to specify that only
trailing non-whitespace content after the top-level JSON value is rejected,
while trailing whitespace remains accepted; retain examples such as concatenated
JSON values or stray bytes.

---

Duplicate comments:
In `@wiki/startup_defaults.md`:
- Line 40: Qualify the request-body limit documentation in
wiki/startup_defaults.md at line 40 to state that known Content-Length requests
over the cap may be rejected with 413 before handler execution, while chunked or
unknown-length bodies can exceed the limit as the handler reads them. Apply the
same distinction to the default and configured-limit verification guidance in
wiki/migrations.md at line 638.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 174320ba-5e1a-4852-a85c-83ad60412973

📥 Commits

Reviewing files that changed from the base of the PR and between 1469675 and 83f2449.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (18)
  • config.example.yaml
  • config/config.go
  • config/config_test.go
  • config/types.go
  • config/validation.go
  • config/validation_test.go
  • go.mod
  • renovate.json
  • server/constants.go
  • server/handler.go
  • server/handler_test.go
  • server/middleware.go
  • server/middleware_test.go
  • server/server.go
  • server/server_test.go
  • wiki/migrations.md
  • wiki/observability.md
  • wiki/startup_defaults.md

Comment thread config/config_test.go
Comment thread wiki/migrations.md Outdated
- migrations.md C51.3 detect: use `git grep` to scan all tracked config
  sources (nested paths, .yml, env), not just root config*.yaml.
- migrations.md C51.2 (gist/gate/verify): narrow "trailing bytes" to trailing
  NON-whitespace — trailing whitespace still binds.
- observability.md: span name keeps the request method (<METHOD> placeholder),
  not always GET (a wrong-method request is POST /<prefix>/*).
- startup_defaults.md: distinguish known-Content-Length rejection (before the
  handler) from chunked/unknown-length (limited reader trips during the read).
- config_test.go: clearEnvironmentVariables now unsets SERVER_BODYLIMIT so the
  default assertion is isolated from the ambient environment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@gaborage
gaborage merged commit 3e6f201 into main Jul 15, 2026
25 checks passed
@gaborage
gaborage deleted the fix/echo-530-group404-guard branch July 15, 2026 19:51
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.

1 participant