|
1 | 1 | # Changelog |
2 | 2 |
|
3 | | -## v4.15.0 - TBD |
| 3 | +## v4.15.0 - 2026-01-01 |
| 4 | + |
| 5 | + |
| 6 | +**Security** |
| 7 | + |
| 8 | +NB: **If your application relies on cross-origin or same-site (same subdomain) requests do not blindly push this version to production** |
| 9 | + |
| 10 | + |
| 11 | +The CSRF middleware now supports the [**Sec-Fetch-Site**](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site) header as a modern, defense-in-depth approach to [CSRF |
| 12 | +protection](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#fetch-metadata-headers), implementing the OWASP-recommended Fetch Metadata API alongside the traditional token-based mechanism. |
| 13 | + |
| 14 | +**How it works:** |
| 15 | + |
| 16 | +Modern browsers automatically send the `Sec-Fetch-Site` header with all requests, indicating the relationship |
| 17 | +between the request origin and the target. The middleware uses this to make security decisions: |
| 18 | + |
| 19 | +- **`same-origin`** or **`none`**: Requests are allowed (exact origin match or direct user navigation) |
| 20 | +- **`same-site`**: Falls back to token validation (e.g., subdomain to main domain) |
| 21 | +- **`cross-site`**: Blocked by default with 403 error for unsafe methods (POST, PUT, DELETE, PATCH) |
| 22 | + |
| 23 | +For browsers that don't send this header (older browsers), the middleware seamlessly falls back to |
| 24 | +traditional token-based CSRF protection. |
| 25 | + |
| 26 | +**New Configuration Options:** |
| 27 | +- `TrustedOrigins []string`: Allowlist specific origins for cross-site requests (useful for OAuth callbacks, webhooks) |
| 28 | +- `AllowSecFetchSiteFunc func(echo.Context) (bool, error)`: Custom logic for same-site/cross-site request validation |
| 29 | + |
| 30 | +**Example:** |
| 31 | + ```go |
| 32 | + e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{ |
| 33 | + // Allow OAuth callbacks from trusted provider |
| 34 | + TrustedOrigins: []string{"https://oauth-provider.com"}, |
| 35 | + |
| 36 | + // Custom validation for same-site requests |
| 37 | + AllowSecFetchSiteFunc: func(c echo.Context) (bool, error) { |
| 38 | + // Your custom authorization logic here |
| 39 | + return validateCustomAuth(c), nil |
| 40 | + // return true, err // blocks request with error |
| 41 | + // return true, nil // allows CSRF request through |
| 42 | + // return false, nil // falls back to legacy token logic |
| 43 | + }, |
| 44 | + })) |
| 45 | + ``` |
| 46 | +PR: https://github.com/labstack/echo/pull/2858 |
| 47 | + |
| 48 | +**Type-Safe Generic Parameter Binding** |
| 49 | + |
| 50 | +* Added generic functions for type-safe parameter extraction and context access by @aldas in https://github.com/labstack/echo/pull/2856 |
| 51 | + |
| 52 | + Echo now provides generic functions for extracting path, query, and form parameters with automatic type conversion, |
| 53 | + eliminating manual string parsing and type assertions. |
| 54 | + |
| 55 | + **New Functions:** |
| 56 | + - Path parameters: `PathParam[T]`, `PathParamOr[T]` |
| 57 | + - Query parameters: `QueryParam[T]`, `QueryParamOr[T]`, `QueryParams[T]`, `QueryParamsOr[T]` |
| 58 | + - Form values: `FormParam[T]`, `FormParamOr[T]`, `FormParams[T]`, `FormParamsOr[T]` |
| 59 | + - Context store: `ContextGet[T]`, `ContextGetOr[T]` |
| 60 | + |
| 61 | + **Supported Types:** |
| 62 | + Primitives (`bool`, `string`, `int`/`uint` variants, `float32`/`float64`), `time.Duration`, `time.Time` |
| 63 | + (with custom layouts and Unix timestamp support), and custom types implementing `BindUnmarshaler`, |
| 64 | + `TextUnmarshaler`, or `JSONUnmarshaler`. |
| 65 | + |
| 66 | + **Example:** |
| 67 | + ```go |
| 68 | + // Before: Manual parsing |
| 69 | + idStr := c.Param("id") |
| 70 | + id, err := strconv.Atoi(idStr) |
| 71 | + |
| 72 | + // After: Type-safe with automatic parsing |
| 73 | + id, err := echo.PathParam[int](c, "id") |
| 74 | + |
| 75 | + // With default values |
| 76 | + page, err := echo.QueryParamOr[int](c, "page", 1) |
| 77 | + limit, err := echo.QueryParamOr[int](c, "limit", 20) |
| 78 | + |
| 79 | + // Type-safe context access (no more panics from type assertions) |
| 80 | + user, err := echo.ContextGet[*User](c, "user") |
| 81 | + ``` |
| 82 | + |
| 83 | +PR: https://github.com/labstack/echo/pull/2856 |
| 84 | + |
| 85 | + |
4 | 86 |
|
5 | 87 | **DEPRECATION NOTICE** Timeout Middleware Deprecated - Use ContextTimeout Instead |
6 | 88 |
|
@@ -37,25 +119,6 @@ e.Use(middleware.Timeout()) |
37 | 119 | e.Use(middleware.ContextTimeout(30 * time.Second)) |
38 | 120 | ``` |
39 | 121 |
|
40 | | -With configuration: |
41 | | -```go |
42 | | -// Before (deprecated): |
43 | | -e.Use(middleware.TimeoutWithConfig(middleware.TimeoutConfig{ |
44 | | - Timeout: 30 * time.Second, |
45 | | - Skipper: func(c echo.Context) bool { |
46 | | - return c.Path() == "/health" |
47 | | - }, |
48 | | -})) |
49 | | - |
50 | | -// After (recommended): |
51 | | -e.Use(middleware.ContextTimeoutWithConfig(middleware.ContextTimeoutConfig{ |
52 | | - Timeout: 30 * time.Second, |
53 | | - Skipper: func(c echo.Context) bool { |
54 | | - return c.Path() == "/health" |
55 | | - }, |
56 | | -})) |
57 | | -``` |
58 | | - |
59 | 122 | **Important Behavioral Differences:** |
60 | 123 |
|
61 | 124 | 1. **Handler cooperation required**: With ContextTimeout, your handlers must check `context.Done()` for cooperative |
@@ -112,6 +175,12 @@ e.GET("/async-task", func(c echo.Context) error { |
112 | 175 | }) |
113 | 176 | ``` |
114 | 177 |
|
| 178 | +**Enhancements** |
| 179 | + |
| 180 | +* Fixes by @aldas in https://github.com/labstack/echo/pull/2852 |
| 181 | +* Generic functions by @aldas in https://github.com/labstack/echo/pull/2856 |
| 182 | +* CRSF with Sec-Fetch-Site checks by @aldas in https://github.com/labstack/echo/pull/2858 |
| 183 | + |
115 | 184 |
|
116 | 185 | ## v4.14.0 - 2025-12-11 |
117 | 186 |
|
|
0 commit comments