Repository navigation
SONARJAVA-6438: S4790 suppress weak hash algorithms on file-sourced data - #6265
Conversation
Do not raise S4790 when weak hash algorithms (MD5, SHA-1) are used exclusively on file-derived data for fingerprinting (ETags, content addressing, dedup keys), where cryptographic collision resistance is irrelevant. Detects file sources via: - NIO: Files.newInputStream/readAllBytes, Channels.newInputStream - Classic IO: FileInputStream, FileReader - Apache Commons: IOUtils.toByteArray, FileUtils.readFileToByteArray - Guava: Files.asByteSource/read, Hashing.md5/sha1 - Spring: MultipartFile.getBytes/getInputStream - Servlet: Part.getInputStream - Stream wrapping: DigestInputStream/DigestOutputStream over file streams Limitation: Source heuristic detects file-derived data but does not prove exclusive use for fingerprinting—file content could be hashed and later applied to security decisions. Accepts false negatives to eliminate noise on common fingerprinting patterns.
4b2863b to
6055cee
Compare
This comment has been minimized.
This comment has been minimized.
…a into the digest The previous approach only looked at arguments of the matched call, so MessageDigest.getInstance and Hashing.md5() were never exempted, and it recursed into the first argument of any method, hiding mixed data. Exemption logic now lives in DataHashingCheck behind a single hook in the base class. MD5 and SHA-1 are exempted only when every byte reaching the digest is proven to come from a file: one-shot DigestUtils calls, chained digest/update/doFinal/hashBytes calls, local digest variables whose every usage is file-fed or neutral, DigestInputStream over a file stream, and Guava ByteSource.hash. Anything unproven is still reported.
|
❌ Ruling needs updating. A fix PR has been created: #6266 Please review and merge it into your branch. |
Ruling Diff SummaryDetected changes in 1 rule files: 1 issues removed, 0 issues added. S4790 (
|
✅ Code review updated (blocking issues remain unresolved).
…etInputStream as file sources
…element writes are not tracked
|
|
||
| private static Use classify(ExpressionTree use) { | ||
| Tree parent = use.parent(); | ||
| if (parent.is(Tree.Kind.MEMBER_SELECT) |
There was a problem hiding this comment.
Could be simplified to:
if (use.parent() instanceof MemberSelectExpressionTree memberSelect
&& memberSelect.expression() == use
&& memberSelect.parent() instanceof MethodInvocationTree invocation) {
return classifyCall(memberSelect.identifier().name(), invocation.arguments());
}
| } | ||
| } | ||
|
|
||
| protected abstract boolean isExempt(MethodInvocationTree mit, InsecureAlgorithm algorithm); |
There was a problem hiding this comment.
Honestly, I don't have a reason to have this class, as we have only one implementation of it. I guess we can get rid on it (having only DataHashingCheck). In this case this method can be turned to static.
There was a problem hiding this comment.
Agreed. Merged into DataHashingCheck and made the method static.
Code Review 👍 Approved with suggestions 7 closed / 8 findings🔴 High risk · File-source heuristics suppress weak-hash alerts, risking missed security-use cases Reworks S4790 to suppress weak hash algorithms when digesting file-sourced data, with comprehensive data-flow tracking for exempted shapes and file sources. The PR description should clarify that the 💡 Quality: PR description says ruling baselines are not updated, but one is📄 its/ruling/src/test/resources/expected/sonar-server/java-S4790.json Under Known limitations, the description says "S4790 ruling and autoscan baselines are expected to change and are not updated in this PR". Commit a4e78f1 does update ✅ 7 closed✅ Bug: Test sample expects no issue where the check still raises one
✅ Security: Recursion into any method's first argument suppresses mixed-data hashes
✅ Bug: Stream wrapping (DigestInputStream, Buffered*) claimed but not handled
✅ Bug: Non-existent
|
| Auto-apply | Compact |
|
|
Was this helpful? React with 👍 / 👎 | Gitar
|




Summary
Do not raise S4790 when MD5 or SHA-1 (including
HmacMD5/HmacSHA1) digest only bytes that are proven to come from a file. File-content fingerprints (ETag, dedup, cache key) have no security decision downstream.How it works
The exemption lives in
DataHashingCheckbehind one hook (isExempt) inAbstractHashAlgorithmChecker. It only applies to MD5, SHA-1 andSHA, and only when the data flow is fully provable. Anything else is still reported.Exempted shapes:
md5*/sha1*, Springmd5Digest*/appendMd5DigestAsHex, with a file-sourced first argumentMessageDigest.getInstance(..).digest(x),Hashing.md5().hashBytes(x),MacdoFinal(x)update/digest/doFinal,new DigestInputStream(fileStream, md)) or neutral (init,reset, no-argdigest()), and at least one usage is file-fedFiles.asByteSource(f).hash(Hashing.md5())File sources:
Files.newInputStream/readAllBytes/readString,Channels.newInputStream(FileChannel),new FileInputStream/FileReader,FileUtils.readFileToByteArray,Files.asByteSource(f).read(). OnlyIOUtils.toByteArray,BufferedInputStream,DigestInputStream,InputStream.readAllBytes()and single-write local variables are followed through. No other method is recursed into.Each exemption in
DataHashingCheckSample.javahas a Noncompliant twin: mixed data, digest passed to another method, digest in a field, digest never fed,ByteArrayInputStream, socket streams, reassigned stream, MD2.Known limitations
MultipartFile.getBytes/getInputStream,Part.getInputStream) are deliberately not treated as file sources, although the ticket lists them. Their content is attacker-controlled, and MD5/SHA-1 chosen-prefix collisions are practical, so a digest over uploads used as a dedup or content-addressing key can still be attacked. They are still reported.DigestOutputStreamis not handled: it digests what is written into it, which is not file content.BatchIndex.java:62expectation was removed fromits/ruling/.../expected/sonar-server/java-S4790.json. No other ruling or autoscan baseline is updated; further changes, if any, depend on CI.byte[]counts as file data only when the digest call is its only usage, because writes into the array (arraycopy,data[i] ^= ...) are not tracked.Test plan
DataHashingCheckTestpasses locally (with and without semantic)DataHashingCheckSample.javacompiles against the test-sources classpath🤖 Generated with Claude Code