{"record":{"id":"9528266c7525c443","repo":"gofiber/fiber","slug":"csrf-chained-extractor-reads-from-the-same-cookie","errorCode":null,"errorMessage":"CSRF: Chained extractor reads from the same cookie '${CookieName}' used for token storage. This completely defeats CSRF protection.","messagePattern":"CSRF: Chained extractor reads from the same cookie '(.+?)' used for token storage\\. This completely defeats CSRF protection\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"middleware/csrf/config.go","lineNumber":197,"sourceCode":"}\n\n// validateExtractorSecurity checks for insecure extractor configurations\nfunc validateExtractorSecurity(cfg *Config) {\n\tif cfg == nil {\n\t\treturn\n\t}\n\t// Check primary extractor\n\tif isInsecureCookieExtractor(cfg.Extractor, cfg.CookieName) {\n\t\tpanic(\"CSRF: Extractor reads from the same cookie '\" + cfg.CookieName +\n\t\t\t\"' used for token storage. This completely defeats CSRF protection.\")\n\t}\n\n\t// Check the full extractor tree so a nested chain cannot hide a fallback\n\t// that reads from the CSRF storage cookie.\n\tif cfg.Extractor.Contains(func(extractor extractors.Extractor) bool {\n\t\treturn isInsecureCookieExtractor(extractor, cfg.CookieName)\n\t}) {\n\t\tpanic(\"CSRF: Chained extractor reads from the same cookie '\" + cfg.CookieName +\n\t\t\t\"' used for token storage. This completely defeats CSRF protection.\")\n\t}\n\n\t// Additional security warnings (non-fatal)\n\tif cfg.Extractor.Source == extractors.SourceQuery || cfg.Extractor.Source == extractors.SourceParam {\n\t\tlog.Warnf(\"[CSRF WARNING] Using %v extractor - URLs may be logged\", cfg.Extractor.Source)\n\t}\n}\n\n// isInsecureCookieExtractor checks if an extractor unsafely reads from the CSRF cookie\nfunc isInsecureCookieExtractor(extractor extractors.Extractor, cookieName string) bool {\n\tif extractor.Source == extractors.SourceCookie {\n\t\t// Exact match - definitely insecure\n\t\tif extractor.Key == cookieName {\n\t\t\treturn true\n\t\t}\n\n\t\t// Case-insensitive match - potentially confusing, warn but don't panic","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/gofiber/fiber/blob/a105acad6c1e4576a77f01e02973f67e962bb58d/middleware/csrf/config.go#L179-L215","documentation":"Even when the top-level CSRF extractor is not the storage cookie, fiber walks the entire extractor tree via Extractor.Contains(...) and panics if ANY nested extractor in a chain (e.g. a Fallback, First, or Chain) reads from the storage cookie. This prevents hiding an insecure fallback behind a secure primary extractor. The predicate reuses isInsecureCookieExtractor against each node's Source/CookieName.","triggerScenarios":"csrf.New(csrf.Config{ CookieName: \"csrf\", Extractor: extractors.Chain(extractors.Header(\"X-CSRF-Token\"), extractors.Cookie(\"csrf\")) }) — a chain whose primary is safe but whose fallback reads the storage cookie. Also any composite (First, Fallback) that includes a SourceCookie node matching cfg.CookieName.","commonSituations":"Building a tolerant extractor that tries a header first and falls back to a cookie, accidentally naming the fallback cookie the same as the storage cookie; chaining third-party extractors that include a cookie reader; refactoring the extractor tree without re-checking every leaf.","solutions":["Audit every leaf of a composite extractor: no SourceCookie leaf may target cfg.CookieName.","Use a distinct readable cookie name for any cookie-based fallback (separate from the HttpOnly storage cookie).","Prefer a header/form primary and a non-storage-cookie fallback; remove cookie fallbacks that point at the storage cookie."],"exampleFix":"// before\ncsrf.New(csrf.Config{\n    CookieName: \"csrf\",\n    Extractor: extractors.First(\n        extractors.Header(\"X-CSRF-Token\"),\n        extractors.Cookie(\"csrf\"), // insecure fallback\n    ),\n})\n// after\ncsrf.New(csrf.Config{\n    CookieName: \"csrf\",\n    Extractor: extractors.First(\n        extractors.Header(\"X-CSRF-Token\"),\n        extractors.Cookie(\"csrf_readable\"), // different cookie\n    ),\n})","handlingStrategy":"validation","validationCode":"// Walk the extractor tree and reject any leaf reading the storage cookie.\nif cfg.Extractor.Contains(func(e extractors.Extractor) bool {\n    return e != nil && e.Source == extractors.SourceCookie && e.CookieName == cfg.CookieName\n}) {\n    return errors.New(\"CSRF extractor chain must not read the storage cookie\")\n}","typeGuard":null,"tryCatchPattern":"defer func() {\n    if r := recover(); r != nil {\n        log.Fatalf(\"insecure CSRF extractor chain: %v\", r)\n    }\n}()\ncsrf.New(cfg)","preventionTips":["Audit every leaf of a composite extractor (Chain/First/Fallback) for the storage cookie name.","Use a different, non-HttpOnly cookie name for any cookie-based fallback.","Prefer header/form extractors; minimize cookie-based token reads."],"tags":["middleware","csrf","security","extractor","chained","startup"],"backgroundTag":null,"analyzedSha":"a105acad6c1e4576a77f01e02973f67e962bb58d","analyzedAt":"2026-08-11T17:33:26.942Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}