Enhance RCON view with enriched player data, kick/ban actions, and live chat log#25
Conversation
…and live chat log Co-authored-by: frasermolyneux <34033625+frasermolyneux@users.noreply.github.com>
|
@copilot - the connected players table should still show the rcon data e.g. number, IP, guid |
… while keeping enhancements Co-authored-by: frasermolyneux <34033625+frasermolyneux@users.noreply.github.com>
Fixed in e6b3439. The table now displays all original RCON data columns:
The enhancements are now layered on top of the original structure rather than replacing columns. |
|
@copilot the code quality check is failing for sonarcloud as the project key is no longer '11:39:04.754 INFO: Check ALM binding of project 'frasermolyneux_xtremeidiots-portal'' it should be '11:39:04.754 INFO: Check ALM binding of project 'frasermolyneux_portal-web' Update it |
Co-authored-by: frasermolyneux <34033625+frasermolyneux@users.noreply.github.com>
Fixed in 5272c4c. Updated the SonarCloud project key in |
|
|
This pull request sets up GitHub code scanning for this repository. Once the scans have completed and the checks have passed, the analysis results for this pull request branch will appear on this overview. Once you merge this pull request, the 'Security' tab will show more code scanning analysis results (for example, for the default branch). Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results. For more information about GitHub code scanning, check out the documentation. |
| if (!kickResult.IsSuccess) | ||
| { | ||
| Logger.LogWarning("Failed to kick player {PlayerName} (slot {PlayerSlot}) from server {ServerId}", | ||
| playerName, playerSlot, id); |
Check warning
Code scanning / CodeQL
Log entries created from user input Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To fix the problem, we should ensure that user-controlled strings used in logs cannot inject new lines or other control characters that would visually forge log entries when viewed as plain text. The typical approach is to normalize user input specifically for logging by removing or replacing newline characters (and optionally other control characters) before passing them to the logger.
In this case, the key user-controlled value is playerName in ServerAdminController.KickRconPlayer. The best way to fix this without changing the method’s external behavior is:
- Introduce a local, sanitized copy of
playerNameat the beginning of theExecuteWithErrorHandlingAsynclambda. - Use this sanitized value only for logging calls; keep using the original
playerNamefor all functional behavior (RCON calls, admin action creation, JSON response to the client) so that there is no change to the app’s behavior from the user’s perspective. - Sanitization can be as simple as stripping
\rand\ncharacters (the primary log-forging vectors). Usingstring.Replaceis sufficient and does not require new dependencies.
Concretely:
- In
ServerAdminController.KickRconPlayer, inside theExecuteWithErrorHandlingAsynclambda (after we knowplayerNameis non-empty), add a new local variable, e.g.var safePlayerName = playerName.Replace("\r", string.Empty).Replace("\n", string.Empty);. - Use
safePlayerNameas the parameter value in theLogger.LogWarningcall on line 400–401 and theLogger.LogErrorcall on line 425–426, leaving other usages ofplayerNameuntouched.
No changes are needed in BaseController for this specific issue.
| @@ -390,6 +390,9 @@ | ||
| return Json(new { success = false, error = "InvalidInput", message = "Invalid player data provided" }); | ||
| } | ||
|
|
||
| // Sanitize player name for safe logging (avoid log forging via newlines) | ||
| var safePlayerName = playerName.Replace("\r", string.Empty).Replace("\n", string.Empty); | ||
|
|
||
| try | ||
| { | ||
| // Kick the player via RCON using slot number | ||
| @@ -398,7 +401,7 @@ | ||
| if (!kickResult.IsSuccess) | ||
| { | ||
| Logger.LogWarning("Failed to kick player {PlayerName} (slot {PlayerSlot}) from server {ServerId}", | ||
| playerName, playerSlot, id); | ||
| safePlayerName, playerSlot, id); | ||
| return Json(new { success = false, error = "RconFailed", message = "Failed to kick player from server" }); | ||
| } | ||
|
|
||
| @@ -423,7 +426,7 @@ | ||
| catch (Exception ex) | ||
| { | ||
| Logger.LogError(ex, "Error kicking player {PlayerName} (slot {PlayerSlot}) from server {ServerId}", | ||
| playerName, playerSlot, id); | ||
| safePlayerName, playerSlot, id); | ||
| return Json(new { success = false, error = "Exception", message = "An error occurred while kicking the player" }); | ||
| } | ||
| }, nameof(KickRconPlayer)); |
| catch (Exception ex) | ||
| { | ||
| Logger.LogError(ex, "Error kicking player {PlayerName} (slot {PlayerSlot}) from server {ServerId}", | ||
| playerName, playerSlot, id); |
Check warning
Code scanning / CodeQL
Log entries created from user input Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
In general, to prevent log forging when logging user input, normalize or encode that input before passing it to the logger. For plain-text logs, this typically means removing or replacing newline (\r, \n) and other control characters so that a malicious value cannot visually break log structure or inject fake entries. For HTML-rendered logs, HTML encoding would be appropriate.
For this specific case, we should sanitize playerName locally in the KickRconPlayer action before it is passed into any logging calls. The simplest, low-impact approach is to create a sanitized local variable that strips newline characters from playerName (e.g., using Replace("\r", "") and Replace("\n", "")) and use that variable only in the logger calls. This preserves the original playerName for business logic such as telemetry properties, admin action creation, and JSON responses, thereby avoiding changes in user-visible behavior or downstream integrations.
Concretely, in src/XtremeIdiots.Portal.Web/Controllers/ServerAdminController.cs within KickRconPlayer, we will:
- Introduce a local variable, e.g.,
var safePlayerNameForLogging = (playerName ?? string.Empty).Replace("\r", string.Empty).Replace("\n", string.Empty);inside thetryblock (after the null/whitespace check). - Update the
Logger.LogWarningcall at lines 400–401 to usesafePlayerNameForLogginginstead ofplayerName. - Update the
Logger.LogErrorcall at lines 425–426 to usesafePlayerNameForLogginginstead ofplayerName.
No additional imports are required; we rely only on string.Replace, which is part of the standard library. We don’t change BaseController because the taint there is only from the delegate itself, and the concrete vulnerable sink is the use of playerName in ServerAdminController.
| @@ -392,13 +392,18 @@ | ||
|
|
||
| try | ||
| { | ||
| // Sanitize player name for logging to prevent log forging | ||
| var safePlayerNameForLogging = (playerName ?? string.Empty) | ||
| .Replace("\r", string.Empty) | ||
| .Replace("\n", string.Empty); | ||
|
|
||
| // Kick the player via RCON using slot number | ||
| var kickResult = await serversApiClient.Rcon.V1.KickPlayer(id, playerSlot); | ||
|
|
||
| if (!kickResult.IsSuccess) | ||
| { | ||
| Logger.LogWarning("Failed to kick player {PlayerName} (slot {PlayerSlot}) from server {ServerId}", | ||
| playerName, playerSlot, id); | ||
| safePlayerNameForLogging, playerSlot, id); | ||
| return Json(new { success = false, error = "RconFailed", message = "Failed to kick player from server" }); | ||
| } | ||
|
|
||
| @@ -423,7 +422,7 @@ | ||
| catch (Exception ex) | ||
| { | ||
| Logger.LogError(ex, "Error kicking player {PlayerName} (slot {PlayerSlot}) from server {ServerId}", | ||
| playerName, playerSlot, id); | ||
| safePlayerNameForLogging, playerSlot, id); | ||
| return Json(new { success = false, error = "Exception", message = "An error occurred while kicking the player" }); | ||
| } | ||
| }, nameof(KickRconPlayer)); |
| if (!banResult.IsSuccess) | ||
| { | ||
| Logger.LogWarning("Failed to temp ban player {PlayerName} (slot {PlayerSlot}) from server {ServerId}", | ||
| playerName, playerSlot, id); |
Check warning
Code scanning / CodeQL
Log entries created from user input Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
In general, the fix is to sanitize or normalize any user-controlled string before it is written to logs. For plain-text logs, that means removing or replacing newline and other control characters that could break log structure. Here, we should avoid logging playerName directly and instead log a sanitized version that has line breaks removed. This preserves functionality (the name remains visible in logs) while preventing log forging.
The best targeted fix is inside TempBanRconPlayer in ServerAdminController.cs: just before the try block, create a sanitized version of playerName for logging, e.g. var safePlayerNameForLog = playerName.Replace("\r", string.Empty).Replace("\n", string.Empty);. Then, use safePlayerNameForLog instead of playerName in the two logging calls that currently log playerName: the LogWarning inside the if (!banResult.IsSuccess) block and the LogError in the catch block. No behavior visible to the user changes, JSON responses remain the same, and we only touch the shown snippet. No new imports are required because string.Replace is in System, which is already implicitly available.
| @@ -468,6 +468,11 @@ | ||
| return Json(new { success = false, error = "InvalidInput", message = "Invalid player data provided" }); | ||
| } | ||
|
|
||
| // Sanitize player name for logging to prevent log forging via newline/control characters | ||
| var safePlayerNameForLog = playerName | ||
| .Replace("\r", string.Empty) | ||
| .Replace("\n", string.Empty); | ||
|
|
||
| try | ||
| { | ||
| // Ban the player via RCON using slot number (most servers don't have separate temp ban RCON command) | ||
| @@ -476,7 +481,7 @@ | ||
| if (!banResult.IsSuccess) | ||
| { | ||
| Logger.LogWarning("Failed to temp ban player {PlayerName} (slot {PlayerSlot}) from server {ServerId}", | ||
| playerName, playerSlot, id); | ||
| safePlayerNameForLog, playerSlot, id); | ||
| return Json(new { success = false, error = "RconFailed", message = "Failed to temp ban player from server" }); | ||
| } | ||
|
|
||
| @@ -503,7 +508,7 @@ | ||
| catch (Exception ex) | ||
| { | ||
| Logger.LogError(ex, "Error temp banning player {PlayerName} (slot {PlayerSlot}) from server {ServerId}", | ||
| playerName, playerSlot, id); | ||
| safePlayerNameForLog, playerSlot, id); | ||
| return Json(new { success = false, error = "Exception", message = "An error occurred while temp banning the player" }); | ||
| } | ||
| }, nameof(TempBanRconPlayer)); |
| catch (Exception ex) | ||
| { | ||
| Logger.LogError(ex, "Error temp banning player {PlayerName} (slot {PlayerSlot}) from server {ServerId}", | ||
| playerName, playerSlot, id); |
Check warning
Code scanning / CodeQL
Log entries created from user input Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
In general, to fix log-forging vulnerabilities you should sanitize or encode any user-controlled value before logging it. For plain-text logs, the key step is to remove or normalize newline and other control characters so an attacker cannot inject fake log lines or break the log format. For HTML-displayed logs, HTML-encode the value before logging or before display.
For this specific case, we should avoid passing raw playerName into Logger.LogError and instead pass a sanitized version that has line breaks and other control characters removed. The rest of the log message structure should remain unchanged. The best minimal fix is to introduce a small sanitization step inside TempBanRconPlayer just before logging. We can, for example, create a sanitizedPlayerName local variable by replacing \r and \n with empty strings (and optionally trimming), then log that instead of the raw playerName. This keeps the functionality the same from a business perspective (admins still see the player name) but prevents basic log forging. No new imports are needed; we can use string.Replace which is already available.
Concretely:
- In
src/XtremeIdiots.Portal.Web/Controllers/ServerAdminController.cs, inside thecatch (Exception ex)block ofTempBanRconPlayer, replace the direct use ofplayerNameinLogger.LogErrorwith a sanitized version created just above the logging call:- Add
var sanitizedPlayerName = playerName?.Replace("\r", string.Empty).Replace("\n", string.Empty); - Pass
sanitizedPlayerNameinstead ofplayerNametoLogger.LogError.
No changes are required inBaseControllerfor this specific alert.
- Add
| @@ -502,8 +502,9 @@ | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| var sanitizedPlayerName = playerName?.Replace("\r", string.Empty).Replace("\n", string.Empty); | ||
| Logger.LogError(ex, "Error temp banning player {PlayerName} (slot {PlayerSlot}) from server {ServerId}", | ||
| playerName, playerSlot, id); | ||
| sanitizedPlayerName, playerSlot, id); | ||
| return Json(new { success = false, error = "Exception", message = "An error occurred while temp banning the player" }); | ||
| } | ||
| }, nameof(TempBanRconPlayer)); |
| if (!banResult.IsSuccess) | ||
| { | ||
| Logger.LogWarning("Failed to ban player {PlayerName} (slot {PlayerSlot}) from server {ServerId}", | ||
| playerName, playerSlot, id); |
Check warning
Code scanning / CodeQL
Log entries created from user input Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
In general, to fix log forging/injection, sanitize or normalize any user-controlled strings before logging them. For plain-text logs, remove or replace newline and other control characters so an attacker cannot create extra log lines or visually merge fields. For logs that might be rendered as HTML, HTML-encode the user input before logging.
For this specific case, the only problematic value is playerName. We should create a sanitized version of playerName used only for logging, leaving the original playerName unchanged for business logic (API calls, JSON responses, database operations) to avoid altering existing functionality. The simplest robust normalization is to strip \r and \n using Replace, possibly combined with Environment.NewLine. We can do this locally in the BanRconPlayer method right before the first log call that uses playerName. For example:
- Introduce
var safePlayerNameForLog = playerName?.Replace("\r", string.Empty).Replace("\n", string.Empty); - Use
safePlayerNameForLogin theLogger.LogWarningandLogger.LogErrorcalls that currently useplayerName. - Keep
playerNameunchanged in JSON responses, telemetry, and admin action creation, so the user-facing behavior is the same.
No new external dependencies are required; string.Replace is available in the BCL. All changes are confined to the BanRconPlayer method in ServerAdminController.cs.
| @@ -550,13 +550,18 @@ | ||
|
|
||
| try | ||
| { | ||
| // Use a sanitized version of the player name for logging to prevent log forging | ||
| var safePlayerNameForLog = playerName? | ||
| .Replace("\r", string.Empty) | ||
| .Replace("\n", string.Empty); | ||
|
|
||
| // Ban the player via RCON using slot number | ||
| var banResult = await serversApiClient.Rcon.V1.BanPlayer(id, playerSlot); | ||
|
|
||
| if (!banResult.IsSuccess) | ||
| { | ||
| Logger.LogWarning("Failed to ban player {PlayerName} (slot {PlayerSlot}) from server {ServerId}", | ||
| playerName, playerSlot, id); | ||
| safePlayerNameForLog, playerSlot, id); | ||
| return Json(new { success = false, error = "RconFailed", message = "Failed to ban player from server" }); | ||
| } | ||
|
|
||
| @@ -580,8 +579,12 @@ | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| var safePlayerNameForLog = playerName? | ||
| .Replace("\r", string.Empty) | ||
| .Replace("\n", string.Empty); | ||
|
|
||
| Logger.LogError(ex, "Error banning player {PlayerName} (slot {PlayerSlot}) from server {ServerId}", | ||
| playerName, playerSlot, id); | ||
| safePlayerNameForLog, playerSlot, id); | ||
| return Json(new { success = false, error = "Exception", message = "An error occurred while banning the player" }); | ||
| } | ||
| }, nameof(BanRconPlayer)); |
|
|
||
| if (!playerResponse.IsSuccess || playerResponse.Result?.Data?.Items?.Any() != true) | ||
| { | ||
| Logger.LogWarning("Player with GUID {Guid} not found in database, cannot create admin action", playerGuidStr); |
Check warning
Code scanning / CodeQL
Log entries created from user input Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
General fix approach: Ensure user-controlled strings are sanitized before being written to logs. For plain-text logs, it is sufficient to strip or replace newline and carriage-return characters so that a user cannot create additional log lines or visually confuse entries.
Best fix for this code: Sanitize playerGuidStr locally in CreateAdminActionForRconOperationAsync before passing it to Logger.LogWarning and Logger.LogInformation. To avoid changing functionality, we should:
- Keep using the original
playerGuidStrfor lookups and business logic. - Use a derived, sanitized value only for logging, removing
\rand\ncharacters (and optionally other control characters if desired, but we’ll minimally address line breaks to match the recommendation).
Concrete change:
- In
ServerAdminController.CreateAdminActionForRconOperationAsync, define asafePlayerGuidForLogvariable that removes newline and carriage-return characters fromplayerGuidStr. - Use this sanitized variable in both log statements at lines 617 and 642 (and also 647, which uses
playerGuidStrin an error log), leaving all other logic untouched.
No additional helper methods or external packages are required; simple string.Replace calls are enough. All changes are within src/XtremeIdiots.Portal.Web/Controllers/ServerAdminController.cs.
| @@ -608,13 +608,19 @@ | ||
| { | ||
| try | ||
| { | ||
| // Sanitize GUID for logging to prevent log forging via newline characters | ||
| var safePlayerGuidForLog = playerGuidStr | ||
| .Replace(Environment.NewLine, string.Empty) | ||
| .Replace("\r", string.Empty) | ||
| .Replace("\n", string.Empty); | ||
|
|
||
| // Try to find existing player profile by searching with GUID | ||
| var playerResponse = await repositoryApiClient.Players.V1.GetPlayers( | ||
| gameType, null, playerGuidStr, 0, 1, PlayersOrder.LastSeenDesc, PlayerEntityOptions.None); | ||
|
|
||
| if (!playerResponse.IsSuccess || playerResponse.Result?.Data?.Items?.Any() != true) | ||
| { | ||
| Logger.LogWarning("Player with GUID {Guid} not found in database, cannot create admin action", playerGuidStr); | ||
| Logger.LogWarning("Player with GUID {Guid} not found in database, cannot create admin action", safePlayerGuidForLog); | ||
| return; | ||
| } | ||
|
|
||
| @@ -640,12 +640,12 @@ | ||
| await repositoryApiClient.AdminActions.V1.CreateAdminAction(createAdminActionDto, cancellationToken); | ||
|
|
||
| Logger.LogInformation("Created admin action {ActionType} for player {PlayerName} ({Guid}) by user {UserId}", | ||
| actionType, playerName, playerGuidStr, adminId); | ||
| actionType, playerName, safePlayerGuidForLog, adminId); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| Logger.LogError(ex, "Failed to create admin action for RCON operation on player {PlayerName} ({Guid})", | ||
| playerName, playerGuidStr); | ||
| playerName, safePlayerGuidForLog); | ||
| } | ||
| } | ||
|
|
| await repositoryApiClient.AdminActions.V1.CreateAdminAction(createAdminActionDto, cancellationToken); | ||
|
|
||
| Logger.LogInformation("Created admin action {ActionType} for player {PlayerName} ({Guid}) by user {UserId}", | ||
| actionType, playerName, playerGuidStr, adminId); |
Check warning
Code scanning / CodeQL
Log entries created from user input Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
In general, to prevent log forging when logging user input, normalize or encode that input before passing it to the logger. For plain‑text logs, remove or replace newline and other control characters so the attacker cannot inject extra log lines or confuse log parsers. For logs rendered as HTML, HTML‑encode user input before logging.
Here, the problematic values are playerName (forged player names) used in Logger.LogInformation and Logger.LogError inside CreateAdminActionForRconOperationAsync. The safest, minimal‑impact fix is to use a sanitized version of playerName only for logging, leaving the original playerName untouched for business logic (e.g., topic creation, DTOs). We can implement a small private helper in ServerAdminController to strip newline and carriage return characters (and optionally other control characters) from strings and then call this helper when passing playerName to the logger on lines 642–643 and 647–648.
Concretely:
- In
ServerAdminController, add a private static method, e.g.,SanitizeForLogging(string value), that replaces\rand\nwith empty strings (or spaces). This uses onlystring.Replace, so no new imports are needed. - In
CreateAdminActionForRconOperationAsync, before logging, computevar safePlayerName = SanitizeForLogging(playerName);. - Pass
safePlayerNameinstead ofplayerNameto bothLogger.LogInformationandLogger.LogError. - Keep all other uses of
playerNameunchanged to avoid altering existing functionality.
This change is localized to ServerAdminController.cs within the shown snippet and addresses all three CodeQL variants that concern logging of playerName.
| @@ -639,13 +639,16 @@ | ||
|
|
||
| await repositoryApiClient.AdminActions.V1.CreateAdminAction(createAdminActionDto, cancellationToken); | ||
|
|
||
| var safePlayerName = SanitizeForLogging(playerName); | ||
|
|
||
| Logger.LogInformation("Created admin action {ActionType} for player {PlayerName} ({Guid}) by user {UserId}", | ||
| actionType, playerName, playerGuidStr, adminId); | ||
| actionType, safePlayerName, playerGuidStr, adminId); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| var safePlayerName = SanitizeForLogging(playerName); | ||
| Logger.LogError(ex, "Failed to create admin action for RCON operation on player {PlayerName} ({Guid})", | ||
| playerName, playerGuidStr); | ||
| safePlayerName, playerGuidStr); | ||
| } | ||
| } | ||
|
|
| await repositoryApiClient.AdminActions.V1.CreateAdminAction(createAdminActionDto, cancellationToken); | ||
|
|
||
| Logger.LogInformation("Created admin action {ActionType} for player {PlayerName} ({Guid}) by user {UserId}", | ||
| actionType, playerName, playerGuidStr, adminId); |
Check warning
Code scanning / CodeQL
Log entries created from user input Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
In general, to fix log forging risks, any user-controlled value written to logs should be normalized or encoded to remove characters that can affect log structure (typically \r and \n). For HTML-rendered logs, HTML encoding is also used, but here we only see standard application logging, so stripping line breaks is appropriate.
For this specific case, the risky value is playerGuidStr used in logging within CreateAdminActionForRconOperationAsync. The simplest, least invasive fix is:
- Introduce a local, sanitized version of the GUID inside
CreateAdminActionForRconOperationAsync. - Use this sanitized value:
- For all
Logger.Log*calls in that method. - Optionally also when passing it to
GetPlayers, to ensure consistent normalization (stripping line breaks does not change a valid GUID value).
- For all
We do not need to change method signatures or logging formats, only the value passed to the logger. We also don’t need new imports; string.Replace and Environment.NewLine (or direct \r and \n) are available. To be thorough across platforms, we should remove both \r and \n.
Concretely, inside CreateAdminActionForRconOperationAsync in ServerAdminController.cs:
- At the start of the
tryblock, introduce:
var safePlayerGuidForLog = playerGuidStr
.Replace("\r", string.Empty)
.Replace("\n", string.Empty);- Use
safePlayerGuidForLog:- Instead of
playerGuidStrwhen callingGetPlayers(this is harmless and keeps behavior consistent). - Instead of
playerGuidStrinLogger.LogWarning(not found),Logger.LogInformation(created admin action), andLogger.LogError(failed to create admin action).
- Instead of
No changes are required in BaseController.ExecuteWithErrorHandlingAsync, since it only logs actionName, controllerName, and userId, all of which are under server control here.
| @@ -608,13 +608,18 @@ | ||
| { | ||
| try | ||
| { | ||
| // Normalize player GUID for safe logging and querying | ||
| var safePlayerGuidForLog = playerGuidStr | ||
| .Replace("\r", string.Empty) | ||
| .Replace("\n", string.Empty); | ||
|
|
||
| // Try to find existing player profile by searching with GUID | ||
| var playerResponse = await repositoryApiClient.Players.V1.GetPlayers( | ||
| gameType, null, playerGuidStr, 0, 1, PlayersOrder.LastSeenDesc, PlayerEntityOptions.None); | ||
| gameType, null, safePlayerGuidForLog, 0, 1, PlayersOrder.LastSeenDesc, PlayerEntityOptions.None); | ||
|
|
||
| if (!playerResponse.IsSuccess || playerResponse.Result?.Data?.Items?.Any() != true) | ||
| { | ||
| Logger.LogWarning("Player with GUID {Guid} not found in database, cannot create admin action", playerGuidStr); | ||
| Logger.LogWarning("Player with GUID {Guid} not found in database, cannot create admin action", safePlayerGuidForLog); | ||
| return; | ||
| } | ||
|
|
||
| @@ -640,12 +640,12 @@ | ||
| await repositoryApiClient.AdminActions.V1.CreateAdminAction(createAdminActionDto, cancellationToken); | ||
|
|
||
| Logger.LogInformation("Created admin action {ActionType} for player {PlayerName} ({Guid}) by user {UserId}", | ||
| actionType, playerName, playerGuidStr, adminId); | ||
| actionType, playerName, safePlayerGuidForLog, adminId); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| Logger.LogError(ex, "Failed to create admin action for RCON operation on player {PlayerName} ({Guid})", | ||
| playerName, playerGuidStr); | ||
| playerName, safePlayerGuidForLog); | ||
| } | ||
| } | ||
|
|
| catch (Exception ex) | ||
| { | ||
| Logger.LogError(ex, "Failed to create admin action for RCON operation on player {PlayerName} ({Guid})", | ||
| playerName, playerGuidStr); |
Check failure
Code scanning / CodeQL
Log entries created from user input High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
In general, to prevent log forging, any user-controlled data included in log messages should have newline and other control characters removed or normalized before logging. This preserves the useful content while preventing an attacker from injecting additional apparent log entries or confusing the log layout.
The best targeted fix here is to sanitize playerName (and, ideally, other similar user-controlled fields reused across logs) before it is used in the logging call within CreateAdminActionForRconOperationAsync. We can introduce a small helper method in ServerAdminController that strips \r and \n (and optionally trims) from a string, and then call it when logging playerName in the Logger.LogError (and, for completeness, the Logger.LogInformation right above). This keeps functionality the same for all other uses of playerName (e.g., forum topic creation, DTOs) while making sure data written to logs can’t add extra lines.
Concretely, in src/XtremeIdiots.Portal.Web/Controllers/ServerAdminController.cs:
- Add a private helper method near the bottom of the class, e.g.
private static string SanitizeForLog(string value), which returns the input with\rand\nremoved (or replaced with spaces). - Update the two logging calls inside
CreateAdminActionForRconOperationAsyncto passSanitizeForLog(playerName)instead ofplayerName.
No changes are required in BaseController.cs, and no new external dependencies are needed.
| @@ -640,15 +640,26 @@ | ||
| await repositoryApiClient.AdminActions.V1.CreateAdminAction(createAdminActionDto, cancellationToken); | ||
|
|
||
| Logger.LogInformation("Created admin action {ActionType} for player {PlayerName} ({Guid}) by user {UserId}", | ||
| actionType, playerName, playerGuidStr, adminId); | ||
| actionType, SanitizeForLog(playerName), playerGuidStr, adminId); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| Logger.LogError(ex, "Failed to create admin action for RCON operation on player {PlayerName} ({Guid})", | ||
| playerName, playerGuidStr); | ||
| SanitizeForLog(playerName), playerGuidStr); | ||
| } | ||
| } | ||
|
|
||
| private static string SanitizeForLog(string value) | ||
| { | ||
| if (value is null) | ||
| { | ||
| return string.Empty; | ||
| } | ||
|
|
||
| // Remove newline characters to prevent log forging via line breaks. | ||
| return value.Replace("\r", string.Empty).Replace("\n", string.Empty); | ||
| } | ||
|
|
||
| [HttpGet] | ||
| public async Task<IActionResult> KickPlayer(Guid id, string num, CancellationToken cancellationToken = default) | ||
| { |
| catch (Exception ex) | ||
| { | ||
| Logger.LogError(ex, "Failed to create admin action for RCON operation on player {PlayerName} ({Guid})", | ||
| playerName, playerGuidStr); |
Check failure
Code scanning / CodeQL
Log entries created from user input High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
General approach: sanitize user-controlled strings before including them in log messages so they cannot inject line breaks or other control characters into log output. For plain-text log outputs, removing \r and \n is sufficient and does not affect typical GUID formats. We only need to sanitize the value used in logging; we can still use the original value for lookups and business logic so existing functionality is preserved.
Best concrete fix for this code:
- In
CreateAdminActionForRconOperationAsync(inServerAdminController), introduce a local variable that holds a sanitized version ofplayerGuidStrspecifically for use in logging. - Sanitize by removing carriage return and newline characters using
Replacecalls. This is simple, works without new dependencies, and matches the guidance in the background (useString.Replaceto remove line endings). - Use this sanitized variable instead of
playerGuidStrin all logging calls inside this method:Logger.LogWarning("Player with GUID {Guid} not found..., currently loggingplayerGuidStr.Logger.LogInformation("Created admin action ... ({Guid}) ..., currently loggingplayerGuidStr.Logger.LogError(ex, "Failed to create admin action ... ({Guid})", playerName, playerGuidStr);, currently loggingplayerGuidStr.
- Do not change the value passed to
GetPlayersor any other non-logging operations to avoid altering existing behavior.
No changes are needed in BaseController; the taint through ExecuteWithErrorHandlingAsync is just the control flow path, not a log sink using the tainted parameter.
| @@ -606,6 +606,11 @@ | ||
| CancellationToken cancellationToken, | ||
| DateTime? expires = null) | ||
| { | ||
| // Sanitize player GUID for safe logging (remove line breaks to prevent log forging) | ||
| var safePlayerGuidForLog = playerGuidStr? | ||
| .Replace("\r", string.Empty, StringComparison.Ordinal) | ||
| .Replace("\n", string.Empty, StringComparison.Ordinal); | ||
|
|
||
| try | ||
| { | ||
| // Try to find existing player profile by searching with GUID | ||
| @@ -614,7 +619,7 @@ | ||
|
|
||
| if (!playerResponse.IsSuccess || playerResponse.Result?.Data?.Items?.Any() != true) | ||
| { | ||
| Logger.LogWarning("Player with GUID {Guid} not found in database, cannot create admin action", playerGuidStr); | ||
| Logger.LogWarning("Player with GUID {Guid} not found in database, cannot create admin action", safePlayerGuidForLog); | ||
| return; | ||
| } | ||
|
|
||
| @@ -640,12 +645,12 @@ | ||
| await repositoryApiClient.AdminActions.V1.CreateAdminAction(createAdminActionDto, cancellationToken); | ||
|
|
||
| Logger.LogInformation("Created admin action {ActionType} for player {PlayerName} ({Guid}) by user {UserId}", | ||
| actionType, playerName, playerGuidStr, adminId); | ||
| actionType, playerName, safePlayerGuidForLog, adminId); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| Logger.LogError(ex, "Failed to create admin action for RCON operation on player {PlayerName} ({Guid})", | ||
| playerName, playerGuidStr); | ||
| playerName, safePlayerGuidForLog); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Pull request overview
This pull request significantly enhances the RCON integration by adding enriched player data display, interactive player management actions (kick/temp ban/ban), and a live chat log feature with auto-refresh capabilities. The changes also include a CI/CD fix for the SonarCloud project key.
Changes:
- Enhanced RCON player data with profile lookup, geolocation (country codes/flags), and risk assessment (VPN/Proxy detection via ProxyCheck service)
- Added kick, temporary ban (7-day default), and permanent ban actions with automatic admin action record creation and forum topic integration
- Implemented live chat log section with 15-second auto-refresh, incremental updates, and proper UI feedback
- Fixed SonarCloud project key from
frasermolyneux_xtremeidiots-portaltofrasermolyneux_portal-web
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/XtremeIdiots.Portal.Web/Controllers/ServerAdminController.cs | Added three new service dependencies (GeoLocation, ProxyCheck, AdminActionTopics), implemented player data enrichment logic, created three new RCON action endpoints (kick/tempban/ban) with authorization and admin action creation, added live chat log endpoint with incremental update support |
| src/XtremeIdiots.Portal.Web/Views/ServerAdmin/ViewRcon.cshtml | Enhanced UI with enriched player information display, added action buttons with confirmation dialogs and toast notifications, implemented live chat log section with auto-refresh JavaScript, improved responsive design and added automation test IDs throughout |
| .github/workflows/codequality.yml | Updated SonarCloud project key for proper code quality tracking |
Comments suppressed due to low confidence (1)
src/XtremeIdiots.Portal.Web/Controllers/ServerAdminController.cs:1025
- Disposable 'StreamReader' is created but not disposed.
var reader = new StreamReader(Request.Body);
| private const string DefaultForumBaseUrl = "https://www.xtremeidiots.com/forums/topic/"; | ||
| private const string DefaultFallbackAdminId = "21145"; |
There was a problem hiding this comment.
The constants DefaultForumBaseUrl and DefaultFallbackAdminId are defined but never used in this controller. These constants should either be used where appropriate (e.g., in admin action creation or configuration retrieval), or removed if they're not needed. Based on the XtremeIdiots Portal Controller Development Guidelines, configuration values should be retrieved using GetConfigurationValue helper method when needed rather than being defined as unused constants.
| private const string DefaultForumBaseUrl = "https://www.xtremeidiots.com/forums/topic/"; | |
| private const string DefaultFallbackAdminId = "21145"; |
| }, | ||
| success: function(result) { | ||
| if (result.success) { | ||
| showToast('error', result.message || 'Player banned successfully', 'Player Banned'); |
There was a problem hiding this comment.
The success toast for banning a player is using 'error' type instead of 'success' type. This is inconsistent with the kick and temp ban actions which correctly use 'success' and 'warning' respectively. The toast type should be 'success' since the ban operation succeeded. Note: The third parameter 'Player Banned' is being passed as the title, which is correct, but the type should match the action result.
| showToast('error', result.message || 'Player banned successfully', 'Player Banned'); | |
| showToast('success', result.message || 'Player banned successfully', 'Player Banned'); |
| { | ||
| return await ExecuteWithErrorHandlingAsync(async () => | ||
| { | ||
| var (actionResult, gameServerData) = await GetAuthorizedGameServerAsync(id, nameof(GetServerLiveChatLog), cancellationToken); |
There was a problem hiding this comment.
This assignment to gameServerData is useless, since its value is never read.
| var (actionResult, gameServerData) = await GetAuthorizedGameServerAsync(id, nameof(GetServerLiveChatLog), cancellationToken); | |
| var (actionResult, _) = await GetAuthorizedGameServerAsync(id, nameof(GetServerLiveChatLog), cancellationToken); |


RCON Integration Enhancement Implementation
Completed Features
Phase 1: Enhanced Player Information ✅
GetRconPlayers)Phase 2: Player Action Buttons (Kick/Ban) ✅
KickRconPlayerendpoint with RCON integrationTempBanRconPlayerendpoint with 7-day default durationBanRconPlayerendpoint for permanent bansPhase 3: Live Chat Log Section ✅
GetServerLiveChatLogendpoint with:CI/CD Fixes ✅
frasermolyneux_xtremeidiots-portaltofrasermolyneux_portal-webTable Structure
The RCON player table now displays:
Technical Implementation Details
Controller Changes (
ServerAdminController.cs)IGeoLocationApiClient,IProxyCheckService,IAdminActionTopicsGetRconPlayersto enrich player data with profiles, geolocation, and proxy check infoView Changes (
ViewRcon.cshtml)Security & Authorization
AuthPolicies.CreateAdminActionPerformance Optimizations
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.