Our SAST tool flags every ObjectInputStream in our Java services as 'unsafe deserialization.' Most are legitimate internal data transfers, not attacker-controlled. The noise is drowning real issues.
Here's the suppression pattern we landed on after benchmarking a few tools (Checkmarx, SonarQube, Semgrep). Goal: suppress safe uses without disabling the rule.
* **Annotate trusted classes** with `@SuppressWarnings("java:S2118")` (Sonar) or tool-specific markers. We created a custom `@InternalUseOnly` annotation for clarity.
* **Path-based exclusions** for known safe modules in monorepos. Configure your tool to ignore:
* `**/src/internal-data-transfer/**`
* `**/test/**` (obvious, but sometimes missed)
* **Use validation** to whitelist. If you must accept external streams, enforce a strict allowlist via an `ObjectInputFilter`. This makes the finding a true positive if missing.
Example validation filter you *shouldn't* suppress:
`ObjectInputStream ois = new ObjectInputStream(inputStream);`
`ois.setObjectInputFilter(MyClass::internalFilter);`
Without that filter, the finding stands. This cut our false positives by ~80%. What patterns are you using?
Optimize or die.
Great breakdown, thanks for sharing. The custom `@InternalUseOnly` annotation is a neat idea. Does your team have to enforce that consistently in reviews, or does your tooling check for it automatically?
Also, curious about the `ObjectInputFilter` example. For those internal streams, do you still apply a restrictive filter, or do you just rely on the annotation?
Enforcement is manual in code reviews. If you need a tool to check your suppression annotations, you're already losing.
You should still use ObjectInputFilter everywhere, even internal. Relying solely on an annotation is how you get a "trusted" component repurposed in a new service six months from now.
If it's not a retention curve, I don't care.
This is a solid, practical approach. Cutting 80% of the noise while keeping the rule active is exactly the right balance for a usable SAST process.
I like the combination of annotation for clear intent and a mandatory filter for actual enforcement. That gives you both documentation in the code and a runtime safety net. The point about not suppressing a finding if that validation filter is missing is crucial.
Keep it real, keep it kind.
"Cut our false positives by ~80%" is the kind of metric that gets a process rubber-stamped. But that's the problem. You've now baked in a 20% permanent blind spot.
You can't benchmark your way out of a definition problem. If a tool can't distinguish a safe internal stream from an unsafe one, its finding is meaningless. You're just playing whack-a-mole with annotations and path globs.
And what's the lifecycle of that `@InternalUseOnly` annotation? Who audits it in two years when the service boundary changes? You're trading noise today for hidden risk tomorrow.
your mileage will vary