diff --git a/backend-nest/src/modules/players/players.service.ts b/backend-nest/src/modules/players/players.service.ts index 003ef81..a998661 100644 --- a/backend-nest/src/modules/players/players.service.ts +++ b/backend-nest/src/modules/players/players.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, BadRequestException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { PlayerAction } from '../../entities/player-action.entity'; @@ -142,14 +142,32 @@ export class PlayersService { } private buildRconCommand(dto: PlayerActionDto): string { + // Defense-in-depth against RCON command injection. The command is a single + // line; an id or reason containing a newline/control char could break the + // framing and inject a second console command. So: + // - the player id must be a safe token (no whitespace/control chars) — a + // permissive charset, not a Rust-only SteamID64 regex, so Conan (Funcom) + // and Dune ids still validate. Reject outright if not. + // - the free-text reason has control chars stripped and is length-capped. + // - duration is coerced to a non-negative integer. + const id = dto.steam_id ?? ''; + if (!/^[A-Za-z0-9_.:-]{1,64}$/.test(id)) { + throw new BadRequestException('Invalid player id'); + } + const safeReason = + (dto.reason ?? 'banned').replace(/[\u0000-\u001F]+/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 200) || 'banned'; + const secs = Number.isFinite(dto.duration_minutes) + ? Math.max(0, Math.floor((dto.duration_minutes as number) * 60)) + : 0; + switch (dto.action_type) { case 'kick': - return `kick ${dto.steam_id}${dto.reason ? ' ' + dto.reason : ''}`; + return `kick ${id}${dto.reason ? ' ' + safeReason : ''}`; case 'ban': // banid — 0 = permanent - return `banid ${dto.steam_id} ${dto.reason ?? 'banned'} ${dto.duration_minutes ? dto.duration_minutes * 60 : 0}`; + return `banid ${id} ${safeReason} ${secs}`; case 'unban': - return `unban ${dto.steam_id}`; + return `unban ${id}`; default: return ''; }