From f338ded34947c9f9d617b1e577d9fed527dea6a6 Mon Sep 17 00:00:00 2001 From: MarkLee131 Date: Sat, 4 Apr 2026 20:57:13 +0800 Subject: [PATCH 1/3] Java: treat hash/encrypt/digest methods as sensitive-log sanitizers The sensitive-log query (CWE-532) lacked sanitizers for hashed or encrypted data, while the sibling cleartext-storage query (CWE-312) already recognized methods with "encrypt", "hash", or "digest" in their names as sanitizers (CleartextStorageQuery.qll:86). This adds an EncryptionBarrier to SensitiveLoggingQuery that applies the same name-based heuristic, making the two queries consistent. Calls like DigestUtils.sha256Hex(password) or hashPassword(secret) are no longer flagged when their results are logged. --- .../2026-04-04-sensitive-log-hash-sanitizer.md | 4 ++++ .../code/java/security/SensitiveLoggingQuery.qll | 13 +++++++++++++ .../security/CWE-532/SensitiveLogInfo.expected | 4 ++++ java/ql/test/query-tests/security/CWE-532/Test.java | 13 +++++++++++++ 4 files changed, 34 insertions(+) create mode 100644 java/ql/lib/change-notes/2026-04-04-sensitive-log-hash-sanitizer.md diff --git a/java/ql/lib/change-notes/2026-04-04-sensitive-log-hash-sanitizer.md b/java/ql/lib/change-notes/2026-04-04-sensitive-log-hash-sanitizer.md new file mode 100644 index 000000000000..7323ab09737a --- /dev/null +++ b/java/ql/lib/change-notes/2026-04-04-sensitive-log-hash-sanitizer.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The `java/sensitive-log` query now treats method calls whose names contain "encrypt", "hash", or "digest" as sanitizers, consistent with the existing treatment in `java/cleartext-storage-in-log`. This reduces false positives when sensitive data is hashed or encrypted before logging. diff --git a/java/ql/lib/semmle/code/java/security/SensitiveLoggingQuery.qll b/java/ql/lib/semmle/code/java/security/SensitiveLoggingQuery.qll index 7058b844cbdb..5f11ae0d214b 100644 --- a/java/ql/lib/semmle/code/java/security/SensitiveLoggingQuery.qll +++ b/java/ql/lib/semmle/code/java/security/SensitiveLoggingQuery.qll @@ -120,6 +120,19 @@ private class DefaultSensitiveLoggerBarrier extends SensitiveLoggerBarrier { } } +/** + * A barrier for sensitive data that has been hashed, encrypted, or digested before logging. + * This is consistent with the treatment of encryption in `CleartextStorageQuery.qll` (CWE-312). + */ +private class EncryptionBarrier extends SensitiveLoggerBarrier { + EncryptionBarrier() { + exists(MethodCall mc | + this.asExpr() = mc and + mc.getMethod().getName().toLowerCase().matches(["%encrypt%", "%hash%", "%digest%"]) + ) + } +} + /** A data-flow configuration for identifying potentially-sensitive data flowing to a log output. */ module SensitiveLoggerConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { source instanceof SensitiveLoggerSource } diff --git a/java/ql/test/query-tests/security/CWE-532/SensitiveLogInfo.expected b/java/ql/test/query-tests/security/CWE-532/SensitiveLogInfo.expected index 54f1e9f8a5aa..af315238bbf3 100644 --- a/java/ql/test/query-tests/security/CWE-532/SensitiveLogInfo.expected +++ b/java/ql/test/query-tests/security/CWE-532/SensitiveLogInfo.expected @@ -3,6 +3,7 @@ | Test.java:12:22:12:52 | ... + ... | Test.java:12:44:12:52 | authToken : String | Test.java:12:22:12:52 | ... + ... | This $@ is written to a log file. | Test.java:12:44:12:52 | authToken | potentially sensitive information | | Test.java:21:22:21:75 | ... + ... | Test.java:21:44:21:52 | authToken : String | Test.java:21:22:21:75 | ... + ... | This $@ is written to a log file. | Test.java:21:44:21:52 | authToken | potentially sensitive information | | Test.java:22:22:22:75 | ... + ... | Test.java:22:44:22:52 | authToken : String | Test.java:22:22:22:75 | ... + ... | This $@ is written to a log file. | Test.java:22:44:22:52 | authToken | potentially sensitive information | +| Test.java:31:21:31:37 | ... + ... | Test.java:31:30:31:37 | password : String | Test.java:31:21:31:37 | ... + ... | This $@ is written to a log file. | Test.java:31:30:31:37 | password | potentially sensitive information | edges | Test.java:11:46:11:53 | password : String | Test.java:11:21:11:53 | ... + ... | provenance | Sink:MaD:2 | | Test.java:12:44:12:52 | authToken : String | Test.java:12:22:12:52 | ... + ... | provenance | Sink:MaD:1 | @@ -10,6 +11,7 @@ edges | Test.java:21:44:21:67 | substring(...) : String | Test.java:21:22:21:75 | ... + ... | provenance | Sink:MaD:1 | | Test.java:22:44:22:52 | authToken : String | Test.java:22:44:22:67 | substring(...) : String | provenance | MaD:3 | | Test.java:22:44:22:67 | substring(...) : String | Test.java:22:22:22:75 | ... + ... | provenance | Sink:MaD:1 | +| Test.java:31:30:31:37 | password : String | Test.java:31:21:31:37 | ... + ... | provenance | Sink:MaD:2 | models | 1 | Sink: org.apache.logging.log4j; Logger; true; error; (String); ; Argument[0]; log-injection; manual | | 2 | Sink: org.apache.logging.log4j; Logger; true; info; (String); ; Argument[0]; log-injection; manual | @@ -25,4 +27,6 @@ nodes | Test.java:22:22:22:75 | ... + ... | semmle.label | ... + ... | | Test.java:22:44:22:52 | authToken : String | semmle.label | authToken : String | | Test.java:22:44:22:67 | substring(...) : String | semmle.label | substring(...) : String | +| Test.java:31:21:31:37 | ... + ... | semmle.label | ... + ... | +| Test.java:31:30:31:37 | password : String | semmle.label | password : String | subpaths diff --git a/java/ql/test/query-tests/security/CWE-532/Test.java b/java/ql/test/query-tests/security/CWE-532/Test.java index 6521f7e2df79..a25becb89bbe 100644 --- a/java/ql/test/query-tests/security/CWE-532/Test.java +++ b/java/ql/test/query-tests/security/CWE-532/Test.java @@ -21,4 +21,17 @@ void test(String password, String authToken, String username, String nullToken, logger.error("Auth failed for: " + authToken.substring(1,5) + "..."); // $ Alert logger.error("Auth failed for: " + authToken.substring(0,8) + "..."); // $ Alert } + + // Tests for hash/encryption sanitizer + void testHashSanitizer(String password, String authToken) { + Logger logger = null; + logger.info("hash: " + hashPassword(password)); // Safe - hashed + logger.info("hash: " + sha256Digest(authToken)); // Safe - digested + logger.info("enc: " + encryptValue(password)); // Safe - encrypted + logger.info("pw: " + password); // $ Alert - not hashed + } + + static String hashPassword(String input) { return input; } + static String sha256Digest(String input) { return input; } + static String encryptValue(String input) { return input; } } From 75162bb9eb4efbd6617cca09483ea777db8338fc Mon Sep 17 00:00:00 2001 From: MarkLee131 Date: Wed, 29 Apr 2026 20:53:58 +0800 Subject: [PATCH 2/3] Update java/ql/test/query-tests/security/CWE-532/Test.java Co-authored-by: Owen Mansel-Chan <62447351+owen-mc@users.noreply.github.com> --- java/ql/test/query-tests/security/CWE-532/Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/test/query-tests/security/CWE-532/Test.java b/java/ql/test/query-tests/security/CWE-532/Test.java index a25becb89bbe..cf1ca0aee68e 100644 --- a/java/ql/test/query-tests/security/CWE-532/Test.java +++ b/java/ql/test/query-tests/security/CWE-532/Test.java @@ -28,7 +28,7 @@ void testHashSanitizer(String password, String authToken) { logger.info("hash: " + hashPassword(password)); // Safe - hashed logger.info("hash: " + sha256Digest(authToken)); // Safe - digested logger.info("enc: " + encryptValue(password)); // Safe - encrypted - logger.info("pw: " + password); // $ Alert - not hashed + logger.info("pw: " + password); // $ Alert // not hashed } static String hashPassword(String input) { return input; } From 51e2a5418b7558c6ef86019afa68c37242b85cd6 Mon Sep 17 00:00:00 2001 From: MarkLee131 Date: Wed, 29 Apr 2026 20:56:36 +0800 Subject: [PATCH 3/3] Java: move EncryptedSensitiveMethodCall into Sanitizers.qll Address review feedback by moving the shared method-name-based encryption/hash/digest check into Sanitizers.qll, and reference it from both CleartextStorageQuery.qll and SensitiveLoggingQuery.qll instead of duplicating the definition. --- .../code/java/security/CleartextStorageQuery.qll | 12 +----------- java/ql/lib/semmle/code/java/security/Sanitizers.qll | 11 +++++++++++ .../code/java/security/SensitiveLoggingQuery.qll | 7 +------ 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/java/ql/lib/semmle/code/java/security/CleartextStorageQuery.qll b/java/ql/lib/semmle/code/java/security/CleartextStorageQuery.qll index 21d82bef657e..83f51f7eedfb 100644 --- a/java/ql/lib/semmle/code/java/security/CleartextStorageQuery.qll +++ b/java/ql/lib/semmle/code/java/security/CleartextStorageQuery.qll @@ -2,6 +2,7 @@ import java private import semmle.code.java.dataflow.TaintTracking +private import semmle.code.java.security.Sanitizers private import semmle.code.java.security.SensitiveActions /** A sink representing persistent storage that saves data in clear text. */ @@ -76,17 +77,6 @@ private class DefaultCleartextStorageSanitizer extends CleartextStorageSanitizer } } -/** - * Method call for encrypting sensitive information. As there are various implementations of - * encryption (reversible and non-reversible) from both JDK and third parties, this class simply - * checks method name to take a best guess to reduce false positives. - */ -private class EncryptedSensitiveMethodCall extends MethodCall { - EncryptedSensitiveMethodCall() { - this.getMethod().getName().toLowerCase().matches(["%encrypt%", "%hash%", "%digest%"]) - } -} - /** Flow configuration for encryption methods flowing to inputs of persistent storage. */ private module EncryptedValueFlowConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node src) { src.asExpr() instanceof EncryptedSensitiveMethodCall } diff --git a/java/ql/lib/semmle/code/java/security/Sanitizers.qll b/java/ql/lib/semmle/code/java/security/Sanitizers.qll index e00071da2d8c..0c5f9b98070a 100644 --- a/java/ql/lib/semmle/code/java/security/Sanitizers.qll +++ b/java/ql/lib/semmle/code/java/security/Sanitizers.qll @@ -63,3 +63,14 @@ class RegexpCheckBarrier extends DataFlow::Node { exists(RegexMatch rm | rm instanceof Annotation | this.asExpr() = rm.getString()) } } + +/** + * A method call for encrypting, hashing, or digesting sensitive information. As there are various + * implementations of encryption (reversible and non-reversible) from both JDK and third parties, + * this class simply checks the method name to take a best guess to reduce false positives. + */ +class EncryptedSensitiveMethodCall extends MethodCall { + EncryptedSensitiveMethodCall() { + this.getMethod().getName().toLowerCase().matches(["%encrypt%", "%hash%", "%digest%"]) + } +} diff --git a/java/ql/lib/semmle/code/java/security/SensitiveLoggingQuery.qll b/java/ql/lib/semmle/code/java/security/SensitiveLoggingQuery.qll index 5f11ae0d214b..f35cae0e67f3 100644 --- a/java/ql/lib/semmle/code/java/security/SensitiveLoggingQuery.qll +++ b/java/ql/lib/semmle/code/java/security/SensitiveLoggingQuery.qll @@ -125,12 +125,7 @@ private class DefaultSensitiveLoggerBarrier extends SensitiveLoggerBarrier { * This is consistent with the treatment of encryption in `CleartextStorageQuery.qll` (CWE-312). */ private class EncryptionBarrier extends SensitiveLoggerBarrier { - EncryptionBarrier() { - exists(MethodCall mc | - this.asExpr() = mc and - mc.getMethod().getName().toLowerCase().matches(["%encrypt%", "%hash%", "%digest%"]) - ) - } + EncryptionBarrier() { this.asExpr() instanceof EncryptedSensitiveMethodCall } } /** A data-flow configuration for identifying potentially-sensitive data flowing to a log output. */