From f7a780ff94e2d8826bf6a078c3d5cfc2dd4bfdfc Mon Sep 17 00:00:00 2001 From: Luke Policinski Date: Fri, 2 Oct 2026 15:23:34 -0400 Subject: [PATCH] bug: launch CS2 servers with -disable_workshop_command_filtering Since CS2's January 2026 update a workshop map drops every "workshop-disallowed" cvar from the cfgs it execs (DISALLOWED WORKSHOP CONVAR in the server log). Match/on-demand and dedicated CS2 servers now boot with the flag that restores the old behaviour; CS:GO servers don't get it. --- .../dedicated-servers.service.spec.ts | 30 +++++++++++++++++-- .../dedicated-servers.service.ts | 6 ++++ ...match-assistant.on-demand-teardown.spec.ts | 14 +++++++++ .../match-assistant.service.ts | 5 +++- 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/dedicated-servers/dedicated-servers.service.spec.ts b/src/dedicated-servers/dedicated-servers.service.spec.ts index e94e6652..02c12d80 100644 --- a/src/dedicated-servers/dedicated-servers.service.spec.ts +++ b/src/dedicated-servers/dedicated-servers.service.spec.ts @@ -723,7 +723,7 @@ describe("DedicatedServersService.setupDedicatedServer", () => { disableServerGuidelines: false, }; - const setup = async (type: string) => { + const deploy = async (type: string, game = "cs2") => { const createNamespacedDeployment = jest.fn().mockResolvedValue({}); const gameModes = { resolveForServer: jest.fn().mockResolvedValue(pluginsOnly), @@ -745,7 +745,7 @@ describe("DedicatedServersService.setupDedicatedServer", () => { type, port: 27015, tv_port: 27020, - game: "cs2", + game, max_players: 10, api_password: "api", rcon_password: "rcon", @@ -781,6 +781,12 @@ describe("DedicatedServersService.setupDedicatedServer", () => { const env: Array<{ name: string; value: string }> = createNamespacedDeployment.mock.calls[0][0].body.spec.template.spec .containers[0].env; + + return env; + }; + + const setup = async (type: string) => { + const env = await deploy(type); const pluginConfigs = env.find((entry) => entry.name === "PLUGIN_CONFIGS"); return pluginConfigs @@ -799,4 +805,24 @@ describe("DedicatedServersService.setupDedicatedServer", () => { it("leaves them to the match on a Ranked server", async () => { expect(await setup("Ranked")).toEqual({}); }); + + const launchParams = async (type: string, game?: string) => + (await deploy(type, game)) + .find((entry) => entry.name === "EXTRA_GAME_PARAMS") + .value.split(" "); + + it.each(["Casual", "Ranked", "Custom"])( + "boots a CS2 %s server with workshop command filtering off", + async (type) => { + expect(await launchParams(type)).toContain( + "-disable_workshop_command_filtering", + ); + }, + ); + + it("leaves the CS2-only flag off a CS:GO server", async () => { + expect(await launchParams("Casual", "csgo")).not.toContain( + "-disable_workshop_command_filtering", + ); + }); }); diff --git a/src/dedicated-servers/dedicated-servers.service.ts b/src/dedicated-servers/dedicated-servers.service.ts index c4e690d2..bb8720ab 100644 --- a/src/dedicated-servers/dedicated-servers.service.ts +++ b/src/dedicated-servers/dedicated-servers.service.ts @@ -352,6 +352,12 @@ export class DedicatedServersService { { name: "EXTRA_GAME_PARAMS", value: [ + // CS2 (since its January 2026 update) drops + // "workshop-disallowed" cvars from every cfg exec'd + // on a workshop map without it. + server.game === "csgo" + ? null + : "-disable_workshop_command_filtering", `-maxplayers ${server.type === "Ranked" ? 16 : server.max_players}`, `+map ${startMap && !startMap.workshop_map_id ? startMap.name : "de_dust2"}`, ...DedicatedServersService.launchMode( diff --git a/src/matches/match-assistant/match-assistant.on-demand-teardown.spec.ts b/src/matches/match-assistant/match-assistant.on-demand-teardown.spec.ts index 82e4ebdd..54fe6373 100644 --- a/src/matches/match-assistant/match-assistant.on-demand-teardown.spec.ts +++ b/src/matches/match-assistant/match-assistant.on-demand-teardown.spec.ts @@ -657,6 +657,20 @@ describe("MatchAssistantService — on-demand server teardown", () => { }); }); + it("boots CS2 with workshop command filtering off", async () => { + await (service as any).assignOnDemandServer("match-1"); + + const [container] = + createNamespacedJob.mock.calls[0][0].body.spec.template.spec.containers; + const params = container.env.find( + (entry: { name: string }) => entry.name === "EXTRA_GAME_PARAMS", + ); + + expect(params.value.split(" ")).toContain( + "-disable_workshop_command_filtering", + ); + }); + it("holds the pool lock past a teardown but not past the retries", async () => { await (service as any).assignOnDemandServer("match-1"); diff --git a/src/matches/match-assistant/match-assistant.service.ts b/src/matches/match-assistant/match-assistant.service.ts index 51c57261..b1c0c2c7 100644 --- a/src/matches/match-assistant/match-assistant.service.ts +++ b/src/matches/match-assistant/match-assistant.service.ts @@ -1257,7 +1257,10 @@ export class MatchAssistantService { }, { name: "EXTRA_GAME_PARAMS", - value: `-maxplayers ${match.max_players_per_lineup * 2 + 3} ${map.workshop_map_id ? `+map de_inferno` : `+map ${map.name}`} +game_type 0 +game_mode ${MatchAssistantService.getGameMode(match.options?.type)} +sv_password ${match.password}${gameMode?.extraGameParams ? ` ${gameMode.extraGameParams}` : ""}`, + // CS2 (since its January 2026 update) drops + // "workshop-disallowed" cvars from every cfg + // exec'd on a workshop map without it. + value: `-disable_workshop_command_filtering -maxplayers ${match.max_players_per_lineup * 2 + 3} ${map.workshop_map_id ? `+map de_inferno` : `+map ${map.name}`} +game_type 0 +game_mode ${MatchAssistantService.getGameMode(match.options?.type)} +sv_password ${match.password}${gameMode?.extraGameParams ? ` ${gameMode.extraGameParams}` : ""}`, }, { name: "SERVER_ID", value: server.id }, { name: "MATCH_ID", value: matchId },