gofiber/fiber · critical

CSRF: Extractor reads from the same cookie

Error message

CSRF: Extractor reads from the same cookie '${CookieName}' used for token storage. This completely defeats CSRF protection.

What it means

The CSRF middleware stores its token in cfg.CookieName and reads it from requests via cfg.Extractor. If the primary extractor's Source is extractors.SourceCookie and it reads the SAME cookie name used for token storage, the middleware would echo the stored token to any requester, completely defeating CSRF protection — so this configuration is rejected at startup. The check (isInsecureCookieExtractor) compares the extractor's CookieName against cfg.CookieName.

Solutions

  1. Read the CSRF token from a DIFFERENT source than the storage cookie — typically a header: extractors.Header("X-CSRF-Token"), or a form field, or a separate readable cookie with a different name.
  2. Keep the storage cookie (CookieName) HttpOnly and never point an extractor at it.
  3. If you need a double-submit cookie pattern, use two distinct cookie names: one HttpOnly storage cookie and one readable cookie for the extractor.

Example fix

// before
csrf.New(csrf.Config{
    CookieName: "csrf_token",
    Extractor:  extractors.Cookie("csrf_token"),
})
// after
csrf.New(csrf.Config{
    CookieName: "csrf_token",          // HttpOnly storage
    Extractor:  extractors.Header("X-CSRF-Token"),
})
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the extractor does not read from the storage cookie.
func extractorReadsStorageCookie(ex extractors.Extractor, storage string) bool {
    return ex != nil && ex.Source == extractors.SourceCookie && ex.CookieName == storage
}
if extractorReadsStorageCookie(cfg.Extractor, cfg.CookieName) {
    return errors.New("CSRF extractor must not read the storage cookie")
}

Try / catch

defer func() {
    if r := recover(); r != nil {
        log.Fatalf("insecure CSRF extractor: %v", r)
    }
}()
csrf.New(cfg)

Prevention

When it happens

Trigger: csrf.New(csrf.Config{ CookieName: "csrf", Extractor: extractors.Cookie("csrf") }) — i.e. the extractor is told to pull the token from the very cookie the middleware uses to store it. Any SourceCookie extractor whose target name matches cfg.CookieName triggers the panic.

Common situations: Switching the extractor source to Cookie for an SPA that sends the token in a cookie, but forgetting the storage cookie must be HttpOnly/separate from the readable token; copy-paste where both fields get the same name; renaming the storage cookie without updating the extractor.

Related errors


AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11). Data as JSON: /api/errors/721026db84be21c1. Report an issue: GitHub.

Appendix: source

Thrown at middleware/csrf/config.go:188

	}
	// Check if Extractor is zero value (since it's a struct)
	if cfg.Extractor.Extract == nil {
		cfg.Extractor = ConfigDefault.Extractor
	}
	// Validate extractor security configurations
	validateExtractorSecurity(&cfg)

	return cfg
}

// validateExtractorSecurity checks for insecure extractor configurations
func validateExtractorSecurity(cfg *Config) {
	if cfg == nil {
		return
	}
	// Check primary extractor
	if isInsecureCookieExtractor(cfg.Extractor, cfg.CookieName) {
		panic("CSRF: Extractor reads from the same cookie '" + cfg.CookieName +
			"' used for token storage. This completely defeats CSRF protection.")
	}

	// Check the full extractor tree so a nested chain cannot hide a fallback
	// that reads from the CSRF storage cookie.
	if cfg.Extractor.Contains(func(extractor extractors.Extractor) bool {
		return isInsecureCookieExtractor(extractor, cfg.CookieName)
	}) {
		panic("CSRF: Chained extractor reads from the same cookie '" + cfg.CookieName +
			"' used for token storage. This completely defeats CSRF protection.")
	}

	// Additional security warnings (non-fatal)
	if cfg.Extractor.Source == extractors.SourceQuery || cfg.Extractor.Source == extractors.SourceParam {
		log.Warnf("[CSRF WARNING] Using %v extractor - URLs may be logged", cfg.Extractor.Source)
	}
}

View on GitHub (pinned to a105acad6c)