From 00838d0bcd5f36b5c85cbef0507d67514cbc2271 Mon Sep 17 00:00:00 2001 From: "Timon.dev" Date: Wed, 3 Dec 2025 21:11:28 +0100 Subject: [PATCH 1/3] Added the cookie-suffix flag and environment variable. Fixes #7544 but is it the best way? --- src/common/http.ts | 4 ++-- src/node/cli.ts | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/common/http.ts b/src/common/http.ts index 5f94c2cd0522..b780c3285ed3 100644 --- a/src/common/http.ts +++ b/src/common/http.ts @@ -24,6 +24,6 @@ export class HttpError extends Error { } } -export enum CookieKeys { - Session = "code-server-session", +export const CookieKeys = { + Session: `code-server-session${process.env?.COOKIE_SUFFIX ? "-" + process.env?.COOKIE_SUFFIX : ""}`, } diff --git a/src/node/cli.ts b/src/node/cli.ts index 0fce9cfbf25f..8c88445aedc7 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -53,6 +53,7 @@ export interface UserProvidedCodeArgs { "disable-getting-started-override"?: boolean "disable-proxy"?: boolean "session-socket"?: string + "cookie-suffix"?: string "link-protection-trusted-domains"?: string[] // locale is used by both VS Code and code-server. locale?: string @@ -172,6 +173,12 @@ export const options: Options> = { "session-socket": { type: "string", }, + "cookie-suffix": { + type: "string", + description: + "Adds a suffix to the cookie. This can prevent a collision of cookies for subdomains, making them explixit. \n" + + "Without this flag, no suffix is used. This can also be set with COOKIE_SUFFIX set to any string.", + }, "disable-file-downloads": { type: "boolean", description: @@ -616,6 +623,10 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config usingEnvPassword = false } + if (process.env.COOKIE_SUFFIX) { + args["cookie-suffix"] = process.env.COOKIE_SUFFIX + } + if (process.env.GITHUB_TOKEN) { args["github-auth"] = process.env.GITHUB_TOKEN } From 5d22aec94d3da1fdaf9f986086e7260db65d2094 Mon Sep 17 00:00:00 2001 From: "Timon.dev" Date: Sat, 6 Dec 2025 18:24:27 +0100 Subject: [PATCH 2/3] Added a prefix for the env-variable --- src/common/http.ts | 2 +- src/node/cli.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/http.ts b/src/common/http.ts index b780c3285ed3..9172c770de45 100644 --- a/src/common/http.ts +++ b/src/common/http.ts @@ -25,5 +25,5 @@ export class HttpError extends Error { } export const CookieKeys = { - Session: `code-server-session${process.env?.COOKIE_SUFFIX ? "-" + process.env?.COOKIE_SUFFIX : ""}`, + Session: `code-server-session${process.env?.CODE_SERVER_COOKIE_SUFFIX ? "-" + process.env?.CODE_SERVER_COOKIE_SUFFIX : ""}`, } diff --git a/src/node/cli.ts b/src/node/cli.ts index 8c88445aedc7..7e4674eaa052 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -177,7 +177,7 @@ export const options: Options> = { type: "string", description: "Adds a suffix to the cookie. This can prevent a collision of cookies for subdomains, making them explixit. \n" + - "Without this flag, no suffix is used. This can also be set with COOKIE_SUFFIX set to any string.", + "Without this flag, no suffix is used. This can also be set with CODE_SERVER_COOKIE_SUFFIX set to any string.", }, "disable-file-downloads": { type: "boolean", @@ -623,8 +623,8 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config usingEnvPassword = false } - if (process.env.COOKIE_SUFFIX) { - args["cookie-suffix"] = process.env.COOKIE_SUFFIX + if (process.env.CODE_SERVER_COOKIE_SUFFIX) { + args["cookie-suffix"] = process.env.CODE_SERVER_COOKIE_SUFFIX } if (process.env.GITHUB_TOKEN) { From 10f94a8b7365a27c1cc594badfb71c21ce917ec0 Mon Sep 17 00:00:00 2001 From: "Timon.dev" Date: Thu, 11 Dec 2025 12:48:23 +0100 Subject: [PATCH 3/3] This should resolve issue #7544 with the addition of the option to set the set the cookie-suffix via environment variable or inline flag --- src/common/http.ts | 4 ++-- src/node/http.ts | 5 +++-- src/node/routes/index.ts | 5 ++++- src/node/routes/login.ts | 3 +-- src/node/routes/logout.ts | 3 +-- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/common/http.ts b/src/common/http.ts index 9172c770de45..d52de90f58e9 100644 --- a/src/common/http.ts +++ b/src/common/http.ts @@ -24,6 +24,6 @@ export class HttpError extends Error { } } -export const CookieKeys = { - Session: `code-server-session${process.env?.CODE_SERVER_COOKIE_SUFFIX ? "-" + process.env?.CODE_SERVER_COOKIE_SUFFIX : ""}`, +export function getCookieSessionName(suffix?: string): string { + return suffix ? `code-server-session-${suffix.replace(/[^a-zA-Z0-9\-]/g, "-")}` : "code-server-session"; } diff --git a/src/node/http.ts b/src/node/http.ts index 6500dc87fadc..e00edccd0210 100644 --- a/src/node/http.ts +++ b/src/node/http.ts @@ -4,7 +4,7 @@ import * as http from "http" import * as net from "net" import qs from "qs" import { Disposable } from "../common/emitter" -import { CookieKeys, HttpCode, HttpError } from "../common/http" +import { HttpCode, HttpError } from "../common/http" import { normalize } from "../common/util" import { AuthType, DefaultedArgs } from "./cli" import { version as codeServerVersion } from "./constants" @@ -40,6 +40,7 @@ declare global { heart: Heart settings: SettingsProvider updater: UpdateProvider + cookieSessionName: string } } } @@ -124,7 +125,7 @@ export const authenticated = async (req: express.Request): Promise => { const passwordMethod = getPasswordMethod(hashedPasswordFromArgs) const isCookieValidArgs: IsCookieValidArgs = { passwordMethod, - cookieKey: sanitizeString(req.cookies[CookieKeys.Session]), + cookieKey: sanitizeString(req.cookies[req.cookieSessionName]), passwordFromArgs: req.args.password || "", hashedPasswordFromArgs: req.args["hashed-password"], } diff --git a/src/node/routes/index.ts b/src/node/routes/index.ts index 28bfc58d3ee7..78e30566867d 100644 --- a/src/node/routes/index.ts +++ b/src/node/routes/index.ts @@ -5,7 +5,7 @@ import { promises as fs } from "fs" import * as path from "path" import * as tls from "tls" import { Disposable } from "../../common/emitter" -import { HttpCode, HttpError } from "../../common/http" +import { getCookieSessionName, HttpCode, HttpError } from "../../common/http" import { plural } from "../../common/util" import { App } from "../app" import { AuthType, DefaultedArgs } from "../cli" @@ -61,6 +61,8 @@ export const register = async ( const settings = new SettingsProvider(path.join(args["user-data-dir"], "coder.json")) const updater = new UpdateProvider("https://api.github.com/repos/coder/code-server/releases/latest", settings) + const cookieSessionName = getCookieSessionName(args["cookie-suffix"]) + const common: express.RequestHandler = (req, _, next) => { // /healthz|/healthz/ needs to be excluded otherwise health checks will make // it look like code-server is always in use. @@ -75,6 +77,7 @@ export const register = async ( req.heart = heart req.settings = settings req.updater = updater + req.cookieSessionName = cookieSessionName next() } diff --git a/src/node/routes/login.ts b/src/node/routes/login.ts index 7a8bb5134c68..db4d1c45dbb3 100644 --- a/src/node/routes/login.ts +++ b/src/node/routes/login.ts @@ -2,7 +2,6 @@ import { Router, Request } from "express" import { promises as fs } from "fs" import { RateLimiter as Limiter } from "limiter" import * as path from "path" -import { CookieKeys } from "../../common/http" import { rootPath } from "../constants" import { authenticated, getCookieOptions, redirect, replaceTemplates } from "../http" import i18n from "../i18n" @@ -95,7 +94,7 @@ router.post<{}, string, { password?: string; base?: string } | undefined, { to?: if (isPasswordValid) { // The hash does not add any actual security but we do it for // obfuscation purposes (and as a side effect it handles escaping). - res.cookie(CookieKeys.Session, hashedPassword, getCookieOptions(req)) + res.cookie(req.cookieSessionName, hashedPassword, getCookieOptions(req)) const to = (typeof req.query.to === "string" && req.query.to) || "/" return redirect(req, res, to, { to: undefined }) diff --git a/src/node/routes/logout.ts b/src/node/routes/logout.ts index 63d8accbcef9..425b3ce17c8f 100644 --- a/src/node/routes/logout.ts +++ b/src/node/routes/logout.ts @@ -1,5 +1,4 @@ import { Router } from "express" -import { CookieKeys } from "../../common/http" import { getCookieOptions, redirect } from "../http" import { sanitizeString } from "../util" @@ -7,7 +6,7 @@ export const router = Router() router.get<{}, undefined, undefined, { base?: string; to?: string }>("/", async (req, res) => { // Must use the *identical* properties used to set the cookie. - res.clearCookie(CookieKeys.Session, getCookieOptions(req)) + res.clearCookie(req.cookieSessionName, getCookieOptions(req)) const to = sanitizeString(req.query.to) || "/" return redirect(req, res, to, { to: undefined, base: undefined, href: undefined })