Skip to content

CSRF middleware doesn't set token in context when Sec-Fetch-Site validation passes (v4.15.0) #2874

@bn4t

Description

@bn4t

Summary

In v4.15.0, when checkSecFetchSiteRequest() returns (true, nil) (e.g., for direct URL navigation where Sec-Fetch-Site: none), the middleware calls return next(c) without setting the CSRF token in context. This breaks handlers that need the token to render forms.

Version

  • Echo v4.15.0

Reproduction

Handler that expects CSRF token in context:

func (h *handler) RegisterPage(c echo.Context) error {
    csrfToken, ok := c.Get("csrf").(string)
    if !ok {
        return echo.NewHTTPError(http.StatusInternalServerError, "CSRF token not found")
    }
    // Render form with CSRF token...
}

Routes setup:

csrfMw := middleware.CSRFWithConfig(middleware.CSRFConfig{TokenLookup: "form:csrf"})
e.GET("/users/register", h.RegisterPage, csrfMw)

Test:

# Works in v4.14.0, fails in v4.15.0
curl -H "Sec-Fetch-Site: none" https://example.com/users/register

# Without header, both versions work
curl https://example.com/users/register

Expected Behavior

  • GET /users/register with Sec-Fetch-Site: none returns 200 with CSRF token in context and cookie set
  • The form can be rendered with the token for subsequent POST

Actual Behavior (v4.15.0)

  • GET /users/register with Sec-Fetch-Site: none returns 500 "CSRF token not found"
  • The token is never set in context because checkSecFetchSiteRequest() returns early

Root Cause

In middleware/csrf.go around lines 164-167, when checkSecFetchSiteRequest() returns (true, nil):

allow, err := checkSecFetchSiteRequest(c)
if err != nil {
    return err
}
if allow {
    return next(c)  // <-- Returns without setting token in context!
}

The middleware correctly validates the request is safe but then returns without:

  1. Generating/retrieving CSRF token
  2. Setting the cookie
  3. Setting the token in context

Suggested Fix

Even for "safe" requests per Sec-Fetch-Site validation, the middleware should still set the token in context and cookie:

if allow {
    // Still need to set token for form rendering
    token := ""
    if k, err := c.Cookie(config.CookieName); err != nil {
        token = randomString(config.TokenLength)
    } else {
        token = k.Value
    }
    // Set cookie and context even for "safe" requests
    c.SetCookie(createCookie(config, token))
    c.Set(config.ContextKey, token)
    c.Response().Header().Add(echo.HeaderVary, echo.HeaderCookie)
    return next(c)
}

Workaround

Downgrade to Echo v4.14.0 until this is fixed.

Impact

This breaks all server-rendered forms that use CSRF middleware when accessed via direct browser navigation (typing URL, bookmarks, clicking external links). The Sec-Fetch-Site: none header is automatically sent by all modern browsers in these scenarios.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions