diff --git a/its/autoscan/src/test/resources/autoscan/autoscan-diff-by-rules.json b/its/autoscan/src/test/resources/autoscan/autoscan-diff-by-rules.json index f6b92127a6e..c305bea2dd1 100644 --- a/its/autoscan/src/test/resources/autoscan/autoscan-diff-by-rules.json +++ b/its/autoscan/src/test/resources/autoscan/autoscan-diff-by-rules.json @@ -3190,5 +3190,11 @@ "hasTruePositives": true, "falseNegatives": 1, "falsePositives": 0 + }, + { + "ruleKey": "S8914", + "hasTruePositives": true, + "falseNegatives": 0, + "falsePositives": 0 } ] diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S8914.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8914.json new file mode 100644 index 00000000000..484d003996c --- /dev/null +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8914.json @@ -0,0 +1,6 @@ +{ + "ruleKey": "S8914", + "hasTruePositives": true, + "falseNegatives": 0, + "falsePositives": 0 +} \ No newline at end of file diff --git a/java-checks-test-sources/default/src/main/java/checks/QuarkusWebSocketSecurityAnnotationCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/QuarkusWebSocketSecurityAnnotationCheckSample.java new file mode 100644 index 00000000000..862d9a567ad --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/QuarkusWebSocketSecurityAnnotationCheckSample.java @@ -0,0 +1,146 @@ +package checks; + +import io.quarkus.security.Authenticated; +import io.quarkus.websockets.next.OnBinaryMessage; +import io.quarkus.websockets.next.OnClose; +import io.quarkus.websockets.next.OnError; +import io.quarkus.websockets.next.OnOpen; +import io.quarkus.websockets.next.OnPongMessage; +import io.quarkus.websockets.next.OnTextMessage; +import io.quarkus.websockets.next.WebSocket; +import jakarta.annotation.security.RolesAllowed; + +class QuarkusWebSocketSecurityAnnotationCheckSample { + + @WebSocket(path = "/secure") + class AuthenticatedOnMethod { + @Authenticated // Noncompliant {{Move this security annotation to the WebSocket endpoint class to secure the HTTP upgrade handshake.}} + @OnTextMessage + String echo(String msg) { + return msg; + } + } + + @WebSocket(path = "/admin") + class RolesAllowedOnMethod { + @RolesAllowed("admin") // Noncompliant {{Move this security annotation to the WebSocket endpoint class to secure the HTTP upgrade handshake.}} + @OnTextMessage + String handleMessage(String msg) { + return msg; + } + } + + @WebSocket(path = "/multi") + class MultipleCallbacksWithSecurity { + @Authenticated // Noncompliant {{Move this security annotation to the WebSocket endpoint class to secure the HTTP upgrade handshake.}} + @OnOpen + void onOpen() { + } + + @RolesAllowed("admin") // Noncompliant {{Move this security annotation to the WebSocket endpoint class to secure the HTTP upgrade handshake.}} + @OnTextMessage + String onMessage(String msg) { + return msg; + } + + @Authenticated // Noncompliant {{Move this security annotation to the WebSocket endpoint class to secure the HTTP upgrade handshake.}} + @OnClose + void onClose() { + } + } + + @WebSocket(path = "/callbacks") + class DifferentCallbackTypes { + @Authenticated // Noncompliant {{Move this security annotation to the WebSocket endpoint class to secure the HTTP upgrade handshake.}} + @OnBinaryMessage + void onBinary(byte[] data) { + } + + @RolesAllowed("user") // Noncompliant {{Move this security annotation to the WebSocket endpoint class to secure the HTTP upgrade handshake.}} + @OnError + void onError(Throwable t) { + } + + @Authenticated // Noncompliant {{Move this security annotation to the WebSocket endpoint class to secure the HTTP upgrade handshake.}} + @OnPongMessage + void onPong() { + } + } + + @WebSocket(path = "/secure-class") + @Authenticated + class ClassLevelAuthenticated { + @OnTextMessage + String echo(String msg) { + return msg; + } + } + + @WebSocket(path = "/admin-class") + @RolesAllowed("admin") + class ClassLevelRolesAllowed { + @OnTextMessage + String handleMessage(String msg) { + return msg; + } + } + + @WebSocket(path = "/hybrid") + @Authenticated + class HybridSecurity { + @OnTextMessage + String normalMessage(String msg) { + return msg; + } + + @RolesAllowed("admin") + @OnTextMessage + String adminMessage(String msg) { + return msg; + } + } + + @WebSocket(path = "/multi-secure") + @RolesAllowed("user") + class SecureClassMultipleMethods { + @OnOpen + void onOpen() { + } + + @OnTextMessage + String onMessage(String msg) { + return msg; + } + + @OnClose + void onClose() { + } + } + + @WebSocket(path = "/public") + class PublicEndpoint { + @OnTextMessage + String echo(String msg) { + return msg; + } + } + + class NotAWebSocket { + @Authenticated + void someMethod() { + } + } + + @WebSocket(path = "/helper") + class SecurityOnNonCallback { + @OnTextMessage + String echo(String msg) { + return process(msg); + } + + @Authenticated + String process(String msg) { + return msg.toUpperCase(); + } + } +} diff --git a/java-checks-test-sources/default/src/main/java/io/quarkus/security/Authenticated.java b/java-checks-test-sources/default/src/main/java/io/quarkus/security/Authenticated.java new file mode 100644 index 00000000000..f4050abef1f --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/io/quarkus/security/Authenticated.java @@ -0,0 +1,11 @@ +package io.quarkus.security; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Authenticated { +} diff --git a/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnBinaryMessage.java b/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnBinaryMessage.java new file mode 100644 index 00000000000..c68c21edc57 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnBinaryMessage.java @@ -0,0 +1,11 @@ +package io.quarkus.websockets.next; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface OnBinaryMessage { +} diff --git a/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnClose.java b/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnClose.java new file mode 100644 index 00000000000..4773f32a727 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnClose.java @@ -0,0 +1,11 @@ +package io.quarkus.websockets.next; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface OnClose { +} diff --git a/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnError.java b/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnError.java new file mode 100644 index 00000000000..1c9c558ed4f --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnError.java @@ -0,0 +1,11 @@ +package io.quarkus.websockets.next; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface OnError { +} diff --git a/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnOpen.java b/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnOpen.java new file mode 100644 index 00000000000..bbb62cd725e --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnOpen.java @@ -0,0 +1,11 @@ +package io.quarkus.websockets.next; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface OnOpen { +} diff --git a/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnPongMessage.java b/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnPongMessage.java new file mode 100644 index 00000000000..30b6d304486 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnPongMessage.java @@ -0,0 +1,11 @@ +package io.quarkus.websockets.next; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface OnPongMessage { +} diff --git a/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnTextMessage.java b/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnTextMessage.java new file mode 100644 index 00000000000..7e7d4112ef4 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/OnTextMessage.java @@ -0,0 +1,11 @@ +package io.quarkus.websockets.next; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface OnTextMessage { +} diff --git a/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/WebSocket.java b/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/WebSocket.java new file mode 100644 index 00000000000..421789c1bec --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/io/quarkus/websockets/next/WebSocket.java @@ -0,0 +1,12 @@ +package io.quarkus.websockets.next; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface WebSocket { + String path(); +} diff --git a/java-checks-test-sources/default/src/main/java/jakarta/annotation/security/RolesAllowed.java b/java-checks-test-sources/default/src/main/java/jakarta/annotation/security/RolesAllowed.java new file mode 100644 index 00000000000..5ab411623b1 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/jakarta/annotation/security/RolesAllowed.java @@ -0,0 +1,12 @@ +package jakarta.annotation.security; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RolesAllowed { + String[] value(); +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/QuarkusWebSocketSecurityAnnotationCheck.java b/java-checks/src/main/java/org/sonar/java/checks/QuarkusWebSocketSecurityAnnotationCheck.java new file mode 100644 index 00000000000..50eeb4759e4 --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/QuarkusWebSocketSecurityAnnotationCheck.java @@ -0,0 +1,108 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import java.util.List; +import org.sonar.check.Rule; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.tree.AnnotationTree; +import org.sonar.plugins.java.api.tree.ClassTree; +import org.sonar.plugins.java.api.tree.MethodTree; +import org.sonar.plugins.java.api.tree.Tree; + +@Rule(key = "S8914") +public class QuarkusWebSocketSecurityAnnotationCheck extends IssuableSubscriptionVisitor { + + private static final String WEBSOCKET_ANNOTATION = "io.quarkus.websockets.next.WebSocket"; + private static final String AUTHENTICATED_ANNOTATION = "io.quarkus.security.Authenticated"; + private static final String ROLES_ALLOWED_ANNOTATION = "jakarta.annotation.security.RolesAllowed"; + + private static final List WEBSOCKET_CALLBACK_ANNOTATIONS = List.of( + "io.quarkus.websockets.next.OnOpen", + "io.quarkus.websockets.next.OnTextMessage", + "io.quarkus.websockets.next.OnBinaryMessage", + "io.quarkus.websockets.next.OnClose", + "io.quarkus.websockets.next.OnError", + "io.quarkus.websockets.next.OnPongMessage" + ); + + private static final List SECURITY_ANNOTATIONS = List.of( + AUTHENTICATED_ANNOTATION, + ROLES_ALLOWED_ANNOTATION + ); + + @Override + public List nodesToVisit() { + return List.of(Tree.Kind.CLASS); + } + + @Override + public void visitNode(Tree tree) { + ClassTree classTree = (ClassTree) tree; + + // Check if the class is annotated with @WebSocket + boolean isWebSocketEndpoint = classTree.modifiers().annotations().stream() + .anyMatch(annotation -> annotation.annotationType().symbolType().is(WEBSOCKET_ANNOTATION)); + + if (!isWebSocketEndpoint) { + return; + } + + // Check if the class has class-level security annotations + boolean hasClassLevelSecurity = classTree.modifiers().annotations().stream() + .anyMatch(annotation -> SECURITY_ANNOTATIONS.stream() + .anyMatch(securityAnnotation -> annotation.annotationType().symbolType().is(securityAnnotation))); + + // If class already has security at class level, no issues to report + if (hasClassLevelSecurity) { + return; + } + + // Check methods for security annotations on WebSocket callbacks + classTree.members().stream() + .filter(member -> member.is(Tree.Kind.METHOD)) + .map(MethodTree.class::cast) + .forEach(this::checkMethod); + } + + private void checkMethod(MethodTree methodTree) { + // Check if the method is a WebSocket callback + boolean isWebSocketCallback = methodTree.modifiers().annotations().stream() + .anyMatch(annotation -> WEBSOCKET_CALLBACK_ANNOTATIONS.stream() + .anyMatch(callbackAnnotation -> annotation.annotationType().symbolType().is(callbackAnnotation))); + + if (!isWebSocketCallback) { + return; + } + + // Find security annotations on the method + List securityAnnotations = methodTree.modifiers().annotations().stream() + .filter(annotation -> SECURITY_ANNOTATIONS.stream() + .anyMatch(securityAnnotation -> annotation.annotationType().symbolType().is(securityAnnotation))) + .toList(); + + // Report issue for each security annotation on the method + for (AnnotationTree securityAnnotation : securityAnnotations) { + reportIssue( + securityAnnotation, + "Move this security annotation to the WebSocket endpoint class to secure the HTTP upgrade handshake.", + List.of(), + null + ); + } + } +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/QuarkusWebSocketSecurityAnnotationCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/QuarkusWebSocketSecurityAnnotationCheckTest.java new file mode 100644 index 00000000000..5c6626b512e --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/QuarkusWebSocketSecurityAnnotationCheckTest.java @@ -0,0 +1,44 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath; + +class QuarkusWebSocketSecurityAnnotationCheckTest { + private static final String SAMPLE_FILE = mainCodeSourcesPath("checks/QuarkusWebSocketSecurityAnnotationCheckSample.java"); + private static final QuarkusWebSocketSecurityAnnotationCheck CHECK = new QuarkusWebSocketSecurityAnnotationCheck(); + + @Test + void test() { + CheckVerifier.newVerifier() + .onFile(SAMPLE_FILE) + .withCheck(CHECK) + .verifyIssues(); + } + + @Test + void testWithoutSemantic() { + CheckVerifier.newVerifier() + .onFile(SAMPLE_FILE) + .withCheck(CHECK) + .withoutSemantic() + .verifyNoIssues(); + } +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8914.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8914.html new file mode 100644 index 00000000000..25292036c58 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8914.html @@ -0,0 +1,72 @@ +

=== Documentation + +* Quarkus WebSockets Next - Security - https://quarkus.io/guides/websockets-next-reference#websocket-next-security[Official Quarkus documentation on securing WebSocket endpoints, explaining HTTP upgrade security] + +* Quarkus WebSockets Next - Secure HTTP Upgrade - https://quarkus.io/guides/websockets-next-reference#secure-http-upgrade[Detailed guidance on securing the HTTP upgrade handshake with security annotations] + + +=== What is the potential impact? + +==== Unauthorized Access + +Unauthorized clients can establish WebSocket connections to endpoints that lack proper authentication during the HTTP upgrade handshake. While individual message-handling callback methods may implement their own authorization checks, the connection remains open, potentially allowing: + +* Access to sensitive real-time data streams +* Information disclosure through connection metadata +* Sending messages to the endpoint +* Exploitation of any unsecured callback methods +* Bypass of intended access controls + +This is particularly severe in applications handling sensitive data or operations through WebSocket communication. + +==== Resource Exhaustion + +Without proper security controls during the HTTP upgrade process: + +* Attackers can open multiple unauthorized connections +* Server resources (memory, CPU, network bandwidth) are consumed maintaining these connections +* Legitimate users may experience degraded service +* Connection pools may be exhausted + +==== False Sense of Security + +Developers may incorrectly believe their WebSocket endpoints are secured when they are not: + +* Security reviews may miss the misconfiguration at the connection level +* Penetration testing might not catch connection-level vulnerabilities if it only tests message-level authorization +* Compliance requirements may not be met despite perceived security measures + +=== Standards + +* OWASP Top 10 2021 - https://owasp.org/Top10/A01_2021-Broken_Access_Control/[OWASP Top 10 2021-A1: Broken Access Control - Improperly secured endpoints allow unauthorized access] + +* OWASP Top 10 2021 - https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/[OWASP Top 10 2021-A7: Identification and Authentication Failures - Authentication not enforced at connection establishment] + + +This is an issue when security annotations that control access based on authentication status or user roles are placed on individual lifecycle or message handling callback methods within a WebSocket endpoint class instead of on the endpoint class itself. + +== Why is this an issue? + +WebSocket connections begin with an HTTP upgrade handshake. When security annotations are placed only on callback methods, they secure those specific methods but *not* the initial HTTP upgrade itself. + +This creates a critical security gap: + +* *Authorization happens too late*: The HTTP upgrade authorization is performed early and only once. Method-level annotations only check authorization when those specific callbacks are invoked, after the connection is already established. + +* *Unauthorized connections succeed*: An attacker can establish a WebSocket connection without proper credentials because the HTTP upgrade is not protected. Even though individual methods might reject unauthorized actions, the connection itself remains open. + +* *False sense of security*: Developers may believe their WebSocket is secured when only methods are annotated, not realizing the handshake is vulnerable. + +When securing WebSocket endpoints, the placement of security annotations determines what gets protected: + +* **Annotations on callback methods**: These only secure the specific callback method execution. The HTTP upgrade handshake remains unsecured, allowing unauthorized clients to establish connections. +* **Annotations on the endpoint class**: These secure the HTTP upgrade handshake itself, preventing unauthorized connections from being established in the first place. + +If not properly secured at the upgrade stage, unauthorized clients can: + +* Establish WebSocket connections +* Consume server resources +* Potentially access data through unsecured callbacks +* Create a false sense of security where developers believe the endpoint is protected + +To properly secure a WebSocket endpoint, security annotations must be placed on the class level, adjacent to the WebSocket endpoint annotation. This ensures authorization is enforced during the HTTP upgrade, preventing unauthorized connections from being established in the first place. This approach is more efficient than checking authorization on every message and prevents bypasses through unsecured callback methods.

\ No newline at end of file diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8914.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8914.json new file mode 100644 index 00000000000..7e6b995dc97 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8914.json @@ -0,0 +1,34 @@ +{ + "title": "Security annotations should be placed on WebSocket endpoint classes, not methods", + "type": "VULNERABILITY", + "status": "ready", + "remediation": { + "func": "Constant/Issue", + "constantCost": "5min" + }, + "tags": [ + "owasp", + "quarkus", + "authorization" + ], + "defaultSeverity": "Critical", + "ruleSpecification": "RSPEC-8914", + "sqKey": "S8914", + "scope": "All", + "defaultQualityProfiles": [ + "Sonar way" + ], + "quickfix": "unknown", + "code": { + "impacts": { + "SECURITY": "HIGH" + }, + "attribute": "COMPLETE" + }, + "securityStandards": { + "OWASP Top 10 2021": [ + "A1", + "A7" + ] + } +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json index a669258ac60..6f2e5a40f82 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json @@ -465,6 +465,9 @@ "S8696", "S8715", "S8745", - "S8786" + "S8786", + "S8911", + "S8914", + "S8924" ] } diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json index 27529004769..adae2df61f8 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json @@ -534,6 +534,7 @@ "S8745", "S8786", "S8911", + "S8914", "S8924" ] } diff --git a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java index 5d255874ee4..555e40aaddb 100644 --- a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java +++ b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java @@ -53,7 +53,7 @@ void profile_is_registered_as_expected() { BuiltInQualityProfilesDefinition.BuiltInQualityProfile actualProfile = profilesPerLanguages.get("java").get("Sonar agentic AI"); assertThat(actualProfile.isDefault()).isFalse(); assertThat(actualProfile.rules()) - .hasSize(465) + .hasSize(469) .extracting(BuiltInQualityProfilesDefinition.BuiltInActiveRule::ruleKey) .doesNotContainAnyElementsOf(List.of( "S101",