Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"ruleKey": "S1874",
"hasTruePositives": true,
"falseNegatives": 257,
"falseNegatives": 259,
"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,84 @@
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: documents a known tokenizer gap. ---
// `AES256_KEY` splits on `_` to ["AES256", "KEY"]. `AES256` has no lowercase letter so
// isAllUppercaseWithLetter returns true and it lowercases to `aes256` as a single token
// (NOT `aes` + `256`). The keyword set holds `aes`, not `aes256`, so no match fires.
// `KEY` -> `[key]`, which is not in the Java security-keyword set.
// Intentional: splitting letters from trailing digits is out of scope here; tracked 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