Conversation
# Conflicts: # src/main/java/gg/modl/backend/infrastructure/filter/V1SecurityConfig.java # src/main/java/gg/modl/backend/infrastructure/ratelimit/RateLimitConfig.java # src/main/java/gg/modl/backend/infrastructure/rest/RESTMappingV1.java # src/test/java/gg/modl/backend/ratelimit/RateLimitConfigTest.java
security fixes
Replay lite
|
@greptileai review pls |
| ticket.setAiAnalysis(result); | ||
| ticket.setUpdatedAt(new Date()); | ||
| ticketRepository.saveEntity(server, ticket); |
There was a problem hiding this comment.
This path now only saves the AI analysis result. When enableAutomatedActions is true and the model reports a violation, executeAutomatedAction is never called, so configured automated punishments or ticket actions silently stop running even though the setting still exists.
| ticket.setAiAnalysis(result); | |
| ticket.setUpdatedAt(new Date()); | |
| ticketRepository.saveEntity(server, ticket); | |
| if (settings.isEnableAutomatedActions() && result.hasViolation()) { | |
| executeAutomatedAction(server, ticket, result, settings); | |
| } | |
| ticket.setAiAnalysis(result); | |
| ticket.setUpdatedAt(new Date()); | |
| ticketRepository.saveEntity(server, ticket); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/main/java/gg/modl/backend/ai/service/AITicketAnalysisService.java
Line: 99-101
Comment:
**Automated actions skipped**
This path now only saves the AI analysis result. When `enableAutomatedActions` is true and the model reports a violation, `executeAutomatedAction` is never called, so configured automated punishments or ticket actions silently stop running even though the setting still exists.
```suggestion
if (settings.isEnableAutomatedActions() && result.hasViolation()) {
executeAutomatedAction(server, ticket, result, settings);
}
ticket.setAiAnalysis(result);
ticket.setUpdatedAt(new Date());
ticketRepository.saveEntity(server, ticket);
```
How can I resolve this? If you propose a fix, please make it concise.| InitReplayUploadResponse response = replayService.initUpload( | ||
| server, | ||
| request.getMcVersion(), | ||
| request.getFileSize() | ||
| ); |
There was a problem hiding this comment.
The v3 upload endpoint calls the overload that sets targetUuid and targetName to null. A v3 client can upload a replay for a player, but the saved ReplayDocument has no target metadata, so player replay lookup by targetUuid cannot find it. The legacy controller passes those fields through.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/main/java/gg/modl/backend/replay/controller/MinecraftReplayV3Controller.java
Line: 38-42
Comment:
**Target metadata dropped**
The v3 upload endpoint calls the overload that sets `targetUuid` and `targetName` to null. A v3 client can upload a replay for a player, but the saved `ReplayDocument` has no target metadata, so player replay lookup by `targetUuid` cannot find it. The legacy controller passes those fields through.
How can I resolve this? If you propose a fix, please make it concise.| Criteria.where(TicketVerificationFields.EXPIRES_AT).gte(now), | ||
| Criteria.where("failedAttempts").lt(MAX_FAILED_ATTEMPTS) |
There was a problem hiding this comment.
Adding failedAttempts < MAX_FAILED_ATTEMPTS makes code verification fail for active verification documents that were created before this field existed. Mongo does not match missing fields with $lt, so a user who received a valid code before deploy can enter the correct code after deploy and still get rejected.
| Criteria.where(TicketVerificationFields.EXPIRES_AT).gte(now), | |
| Criteria.where("failedAttempts").lt(MAX_FAILED_ATTEMPTS) | |
| Criteria.where(TicketVerificationFields.EXPIRES_AT).gte(now), | |
| new Criteria().orOperator( | |
| Criteria.where("failedAttempts").exists(false), | |
| Criteria.where("failedAttempts").is(null), | |
| Criteria.where("failedAttempts").lt(MAX_FAILED_ATTEMPTS) | |
| ) |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/main/java/gg/modl/backend/database/mongo/repository/TicketVerificationMongoRepository.java
Line: 41-42
Comment:
**Old codes stop working**
Adding `failedAttempts < MAX_FAILED_ATTEMPTS` makes code verification fail for active verification documents that were created before this field existed. Mongo does not match missing fields with `$lt`, so a user who received a valid code before deploy can enter the correct code after deploy and still get rejected.
```suggestion
Criteria.where(TicketVerificationFields.EXPIRES_AT).gte(now),
new Criteria().orOperator(
Criteria.where("failedAttempts").exists(false),
Criteria.where("failedAttempts").is(null),
Criteria.where("failedAttempts").lt(MAX_FAILED_ATTEMPTS)
)
```
How can I resolve this? If you propose a fix, please make it concise.| String serverDomain = resolvePanelServerDomain(hello); | ||
| if (serverDomain == null) { | ||
| throw new RealtimeAuthenticationException(ErrorCode.ERROR_CODE_FORBIDDEN, "Missing or invalid tenant scope"); | ||
| } |
There was a problem hiding this comment.
Panel WebSocket auth now requires the tenant domain to be repeated in ClientHello.serverName. A browser connecting from the tenant host with a valid session cookie but no serverName is rejected with “Missing or invalid tenant scope”. The helper that resolves the tenant from Host / X-Forwarded-Host is present in this class but is not used, so this does not match the normal HTTP tenant resolution path.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/main/java/gg/modl/backend/realtime/auth/RealtimeAuthenticator.java
Line: 45-48
Comment:
**Host tenant ignored**
Panel WebSocket auth now requires the tenant domain to be repeated in `ClientHello.serverName`. A browser connecting from the tenant host with a valid session cookie but no `serverName` is rejected with “Missing or invalid tenant scope”. The helper that resolves the tenant from `Host` / `X-Forwarded-Host` is present in this class but is not used, so this does not match the normal HTTP tenant resolution path.
How can I resolve this? If you propose a fix, please make it concise.| TOPIC_MINECRAFT_PUNISHMENT_TYPES, | ||
| TOPIC_MINECRAFT_STAFF_NOTIFICATIONS, | ||
| TOPIC_MINECRAFT_PRESENCE, | ||
| TOPIC_MINECRAFT_PUNISHMENTS, | ||
| TOPIC_MINECRAFT_PLAYER_NOTIFICATIONS, |
There was a problem hiding this comment.
Minecraft topics over-authorized
This now lets any websocket authenticated with the tenant Minecraft API key subscribe to tenant-wide player notifications, staff 2FA pushes, punishments, migration tasks, and stat wipes. The Minecraft principal only carries the server and optional instance id, so there is no player or staff scope to filter these topics. A plugin instance that should only receive its relevant sync data can receive every pushed player notification or staff 2FA verification event for the tenant.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/main/java/gg/modl/backend/realtime/auth/RealtimeTopicAuthorizer.java
Line: 72-76
Comment:
**Minecraft topics over-authorized**
This now lets any websocket authenticated with the tenant Minecraft API key subscribe to tenant-wide player notifications, staff 2FA pushes, punishments, migration tasks, and stat wipes. The Minecraft principal only carries the server and optional instance id, so there is no player or staff scope to filter these topics. A plugin instance that should only receive its relevant sync data can receive every pushed player notification or staff 2FA verification event for the tenant.
How can I resolve this? If you propose a fix, please make it concise.| ) { | ||
| Server server = RequestUtil.getRequestServer(httpRequest); | ||
| minecraftSyncService.applyPresence( | ||
| server, | ||
| MinecraftSyncProtoMapper.toOnlinePlayers(request), | ||
| request.hasServerName() ? request.getServerName() : null, | ||
| Instant.now() | ||
| ); |
There was a problem hiding this comment.
Missing roster clears presence
The new /presence endpoint treats an absent onlinePlayers field as an empty list and immediately calls applyPresence. That path marks every stale player not in the supplied set as offline, so a malformed or partial presence heartbeat with no roster succeeds while setting the server count to 0 and reconciling all players for that server as offline. This endpoint should reject requests without an explicit roster, or use a request shape that can distinguish “no roster sent” from “zero players online.”
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/main/java/gg/modl/backend/player/controller/MinecraftSyncV3Controller.java
Line: 91-98
Comment:
**Missing roster clears presence**
The new `/presence` endpoint treats an absent `onlinePlayers` field as an empty list and immediately calls `applyPresence`. That path marks every stale player not in the supplied set as offline, so a malformed or partial presence heartbeat with no roster succeeds while setting the server count to 0 and reconciling all players for that server as offline. This endpoint should reject requests without an explicit roster, or use a request shape that can distinguish “no roster sent” from “zero players online.”
How can I resolve this? If you propose a fix, please make it concise.| AITicketAnalysisService.AISuggestionResult result = aiTicketAnalysisService.applyAISuggestion(server, ticketId, staffName); | ||
| if (!result.success()) { | ||
| return ResponseEntity.badRequest().body(Map.of("error", result.error())); | ||
| } | ||
| return ResponseEntity.ok(Map.of("success", true)); | ||
| return toAISuggestionResponse(result); |
There was a problem hiding this comment.
Failures return success status
This endpoint now returns an AISuggestionActionResponse directly for both successful and failed applyAISuggestion results. The previous path returned 400 when result.success() was false, but now a failed punishment application returns HTTP 200 with success=false. Existing clients that rely on the non-2xx status can show the action as completed even though the service rejected it.
| AITicketAnalysisService.AISuggestionResult result = aiTicketAnalysisService.applyAISuggestion(server, ticketId, staffName); | |
| if (!result.success()) { | |
| return ResponseEntity.badRequest().body(Map.of("error", result.error())); | |
| } | |
| return ResponseEntity.ok(Map.of("success", true)); | |
| return toAISuggestionResponse(result); | |
| AITicketAnalysisService.AISuggestionResult result = aiTicketAnalysisService.applyAISuggestion(server, ticketId, staffName); | |
| if (!result.success()) { | |
| throw new ValidationException(result.error()); | |
| } | |
| return toAISuggestionResponse(result); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/main/java/gg/modl/backend/settings/controller/PanelSettingsController.java
Line: 360-361
Comment:
**Failures return success status**
This endpoint now returns an `AISuggestionActionResponse` directly for both successful and failed `applyAISuggestion` results. The previous path returned `400` when `result.success()` was false, but now a failed punishment application returns HTTP 200 with `success=false`. Existing clients that rely on the non-2xx status can show the action as completed even though the service rejected it.
```suggestion
AITicketAnalysisService.AISuggestionResult result = aiTicketAnalysisService.applyAISuggestion(server, ticketId, staffName);
if (!result.success()) {
throw new ValidationException(result.error());
}
return toAISuggestionResponse(result);
```
How can I resolve this? If you propose a fix, please make it concise.| AITicketAnalysisService.AISuggestionResult result = aiTicketAnalysisService.dismissAISuggestion(server, ticketId); | ||
| if (!result.success()) { | ||
| return ResponseEntity.badRequest().body(Map.of("error", result.error())); | ||
| } | ||
| return ResponseEntity.ok(Map.of("success", true)); | ||
| return toAISuggestionResponse(result); |
There was a problem hiding this comment.
Dismiss failures return success status
The dismiss path has the same status-code regression as apply. When dismissAISuggestion fails, the controller still returns HTTP 200 with success=false, while the old implementation returned a bad request. A panel caller that only checks the response status can remove or mark the suggestion as dismissed even though the backend did not dismiss it.
| AITicketAnalysisService.AISuggestionResult result = aiTicketAnalysisService.dismissAISuggestion(server, ticketId); | |
| if (!result.success()) { | |
| return ResponseEntity.badRequest().body(Map.of("error", result.error())); | |
| } | |
| return ResponseEntity.ok(Map.of("success", true)); | |
| return toAISuggestionResponse(result); | |
| AITicketAnalysisService.AISuggestionResult result = aiTicketAnalysisService.dismissAISuggestion(server, ticketId); | |
| if (!result.success()) { | |
| throw new ValidationException(result.error()); | |
| } | |
| return toAISuggestionResponse(result); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/main/java/gg/modl/backend/settings/controller/PanelSettingsController.java
Line: 371-372
Comment:
**Dismiss failures return success status**
The dismiss path has the same status-code regression as apply. When `dismissAISuggestion` fails, the controller still returns HTTP 200 with `success=false`, while the old implementation returned a bad request. A panel caller that only checks the response status can remove or mark the suggestion as dismissed even though the backend did not dismiss it.
```suggestion
AITicketAnalysisService.AISuggestionResult result = aiTicketAnalysisService.dismissAISuggestion(server, ticketId);
if (!result.success()) {
throw new ValidationException(result.error());
}
return toAISuggestionResponse(result);
```
How can I resolve this? If you propose a fix, please make it concise.
Confidence Score: 2/5
This should be fixed before merging.
Focus on the realtime topic authorization, the new presence endpoint, and the AI suggestion action responses.
Security Review
RealtimeTopicAuthorizer: Minecraft API-key clients can subscribe to tenant-wide realtime player notification and staff 2FA topics without a player or staff scope.Important Files Changed
Prompt To Fix All With AI
Reviews (2): Last reviewed commit: "websocket realtime + proto-json controll..." | Re-trigger Greptile
Context used: