Skip to content
Open
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"ruleKey": "S1874",
"hasTruePositives": true,
"falseNegatives": 246,
"falseNegatives": 248,
"falsePositives": 0
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"ruleKey": "S2119",
"hasTruePositives": true,
"falseNegatives": 2,
"falseNegatives": 3,
"falsePositives": 0
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"ruleKey": "S2245",
"hasTruePositives": true,
"falseNegatives": 26,
"falseNegatives": 17,
"falsePositives": 0
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
{
"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java": [
287
],
"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionIdManager.java": [
369
]
Expand Down
3 changes: 0 additions & 3 deletions its/ruling/src/test/resources/eclipse-jetty/java-S2245.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
{
"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java": [
287
],
"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionIdManager.java": [
369
]
Expand Down
3 changes: 0 additions & 3 deletions its/ruling/src/test/resources/mall/java-S2245.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
{
"com.macro.mall:mall:mall-portal/src/main/java/com/macro/mall/portal/service/impl/UmsMemberCouponServiceImpl.java": [
91
],
"com.macro.mall:mall:mall-portal/src/main/java/com/macro/mall/portal/service/impl/UmsMemberServiceImpl.java": [
112
]
Expand Down
6 changes: 1 addition & 5 deletions its/ruling/src/test/resources/sonar-server/java-S2245.json
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
{
"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/es/RecoveryIndexer.java": [
92
]
}
{}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package checks;

import java.util.Random;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

// SONARJAVA-6440 Part 1: file imports `org.bouncycastle.*` -> all PRNG calls flagged
// regardless of any per-scope keyword check.
class PseudoRandomCheckCryptoImportSample {

// BouncyCastle reference so the import is used.
static final BouncyCastleProvider PROVIDER = new BouncyCastleProvider();

void noKeywordsInScope() {
int counter = 0;
Random r = new Random(); // Noncompliant {{Make sure that using this pseudorandom number generator is safe here.}}
counter += r.nextInt();
}

double anotherNeutralMethod() {
return Math.random(); // Noncompliant
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package checks;

import java.util.Random;

// SONARJAVA-6440: no crypto import. Random call appears outside any method (field initializer
// and static initializer). Scope falls back to the enclosing class; the class identifiers
// drive the keyword check.

class PseudoRandomCheckFieldScopeSampleSecure {
// Class name contains keyword: `TokenGenerator` tokens -> [token, generator]. `token` matches.
static class TokenGenerator {
static final Random RNG = new Random(); // Noncompliant {{Make sure that using this pseudorandom number generator is safe here.}}
}

// No method scope, but a sibling field name contains a security keyword.
static class Holder {
String password;
static final Random R = new Random(); // Noncompliant
}

// Static initializer: no enclosing method; class identifiers carry the keyword.
static class CipherBundle {
static Random r;
static {
r = new Random(); // Noncompliant
}
}
}

class PseudoRandomCheckFieldScopeSampleNeutral {
// No method, no security keyword anywhere in the enclosing class -> Compliant.
static final Random R = new Random(); // Compliant
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package checks;

import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import org.apache.commons.lang.math.JVMRandom;
import org.apache.commons.lang.math.RandomUtils;
import org.apache.commons.lang.RandomStringUtils;

// SONARJAVA-6440: no crypto import, no security-keyword identifiers in scope ->
// the security-context heuristic suppresses the issue.
class PseudoRandomCheckNoContextSample {

void neutralMethod() {
Random r = new Random(); // Compliant
int v = r.nextInt();

JVMRandom j = new JVMRandom(); // Compliant
double d1 = j.nextDouble();

double d2 = Math.random(); // Compliant
int v2 = ThreadLocalRandom.current().nextInt(); // Compliant

float f1 = RandomUtils.nextFloat(); // Compliant
String s1 = RandomStringUtils.random(1); // Compliant
}

int doWork(int input) {
return input + 1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package checks;

import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.RandomStringUtils;

// SONARJAVA-6440: no crypto import. Security keywords reached via per-scope identifier scan.
class PseudoRandomCheckSecurityKeywordsSample {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: a few edge cases worth adding to the test suite

  1. Digit-containing uppercase identifiersAES256 has no lowercase letter so isAllUppercaseWithLetter returns true and it lowercases to "aes256", which does not match the keyword "aes". A variable AES256_KEY would therefore not trigger the rule. Please add a test to document this (Compliant or Noncompliant — either is fine, but it should be intentional).

  2. Wildcard crypto importimport java.security.*; should trigger Part 1 (see comment on matchesCryptoPrefix). Add a test case in PseudoRandomCheckCryptoImportSample.java.

  3. Nested inner class isolationfindDeclarationScope returns the inner ClassTree first, so a PRNG call in an inner class should not pick up keywords from the outer class. Worth an explicit Compliant test to confirm this isolation is intentional.

  4. Static import of a crypto classimport static javax.crypto.Cipher.ENCRYPT_MODE; is still Tree.Kind.IMPORT, so it should be caught by hasCryptoImport. A targeted test would confirm this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All four added in c1708df: AES256_KEY (Compliant, with comment documenting Dart-faithful behaviour), wildcard import sample, static import sample, inner-class isolation (TokenAware outer + NeutralInner inner). Tokenization gap (digit-suffix + ChaCha20-style) documented on the SONARJAVA-6440 description; cross-language follow-up tracked under APPSEC-3004.


// --- Method-scope keyword tokenized from camelCase (`userPassword` -> [user, password]). ---
void camelCaseLocal() {
String userPassword = "x";
Random r = new Random(); // Noncompliant {{Make sure that using this pseudorandom number generator is safe here.}}
r.nextInt();
}

// --- Method-scope keyword tokenized from snake_case (`user_token` -> [user, token]). ---
void snakeCaseLocal() {
String user_token = "x";
double d = Math.random(); // Noncompliant
}

// --- Method-scope keyword from all-uppercase (`HMAC` -> [hmac]). ---
void upperCaseLocal() {
final int HMAC = 32;
int v = ThreadLocalRandom.current().nextInt(); // Noncompliant
}

// --- Keyword in the method name itself. ---
void encryptPayload() {
float f = RandomUtils.nextFloat(); // Noncompliant
}

// --- Keyword in a parameter name. ---
String randomFromToken(String token) {
return RandomStringUtils.random(1); // Noncompliant
}

// --- Whole-identifier match (`password`). ---
void wholeIdentifierMatch() {
String password = "x";
Random r = new Random(); // Noncompliant
}

// --- No keyword in scope: must NOT be flagged (heuristic suppresses it). ---
void unrelatedScope() {
int counter = 0;
Random r = new Random(); // Compliant
counter += r.nextInt();
}

// --- camelCase that DOES NOT match keyword after tokenization. ---
// `randomBytes` tokenizes to [random, bytes]; neither is in the keyword set
// (the keyword `randombytes` only matches an all-lowercase or all-uppercase literal).
void splitRandomBytes() {
byte[] randomBytes = new byte[16];
Random r = new Random(); // Compliant
r.nextBytes(randomBytes);
}

// --- Digit-suffixed all-uppercase crypto acronym (Dart-faithful behaviour). ---
// `AES256_KEY` splits on `_` to ["AES256", "KEY"]. The "AES256" part has no lowercase
// letter, so isAllUppercaseWithLetter returns true and it is kept as the single token
// `aes256` (NOT split into `aes` + `256`). The keyword set holds `aes`, not `aes256`,
// so no token matches. `KEY` -> `[key]`, but `key` is not in the Java keyword set
// (it is in the Kotlin set; SONARKT-770 catches this case in Kotlin only).
// This is intentional: the tokenizer mirrors DART-276's _splitIntoWords exactly to
// keep cross-language behaviour aligned. Improving the tokenizer to split letters
// from trailing digits would help here AND in cases like `ChaCha20` (where the keyword
// `chacha20` is also unreachable today) but is out of scope for this faithful port.
// Tracked separately under APPSEC-3004.
void digitSuffixedAcronym() {
final int AES256_KEY = 32;
Random r = new Random(); // Compliant
r.nextInt(AES256_KEY);
}
}

Comment thread
Luqmansonar marked this conversation as resolved.
// --- Inner-class scope isolation. ---
// The outer class name `TokenAware` contains the security keyword `token`, but the
// PRNG call lives in an inner class with neutral identifiers. findDeclarationScope
// returns the inner ClassTree first, so the outer's keyword is NOT in scope.
class TokenAware {
static class NeutralInner {
static final Random R = new Random(); // Compliant
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package checks;

import static javax.crypto.Cipher.ENCRYPT_MODE;

import java.util.Random;

// SONARJAVA-6440 Part 1: static import of a crypto class still parses as Tree.Kind.IMPORT,
// so the concatenated name `javax.crypto.Cipher.ENCRYPT_MODE` matches the `javax.crypto.`
// prefix and the file-level crypto-import gate fires.
class PseudoRandomCheckStaticImportSample {

static final int MODE = ENCRYPT_MODE; // retain the static import

void noKeywordsInScope() {
int counter = 0;
Random r = new Random(); // Noncompliant {{Make sure that using this pseudorandom number generator is safe here.}}
counter += r.nextInt();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package checks;

import java.security.*;
import java.util.Random;

// SONARJAVA-6440 Part 1: wildcard import `java.security.*` -> all PRNG calls flagged.
// ExpressionsHelper.concatenate returns "java.security.*", which still starts with the
Comment thread
Luqmansonar marked this conversation as resolved.
// "java.security." prefix, so the file-level crypto-import check fires.
class PseudoRandomCheckWildcardImportSample {

static final KeyPair PROVIDER_KEYPAIR = null; // forces the wildcard import to be retained

void noKeywordsInScope() {
int counter = 0;
Random r = new Random(); // Noncompliant {{Make sure that using this pseudorandom number generator is safe here.}}
counter += r.nextInt();
}

double anotherNeutralMethod() {
return Math.random(); // Noncompliant
}
}
Loading
Loading