The README said "implementing security best practices."
That line has been sitting in the pgmpofu/mflix repository since 2019. A MongoDB-backed movie browsing application with user registration, authentication, JWT-based sessions, and full CRUD operations. Built as part of a MongoDB University course. Described, in my own words, as implementing security best practices.
I ran Snyk against it last week.
188 vulnerabilities. 10 Critical. 99 High. 59 Medium. 20 Low.
Every single one fixable. None of them there when I wrote that README.
This is the first article in a series about what happens when you apply modern software composition analysis to a Java project that hasn't been touched in six years — what Snyk found, what I fixed, what I chose not to fix, and what the before-and-after security posture actually looks like in measurable terms.
What the Project Is
MFlix is a Spring Boot Java application backed by MongoDB. The core functionality:
- Movie search — basic and complex queries against a MongoDB collection
- User registration and authentication
- JWT-based session management using
io.jsonwebtoken:jjwt - Comment posting on movie entries
- Analytical reporting against the movie dataset
- Spring Security for access control
It's not a toy. It has real authentication flows, real database operations, and real dependency complexity. The
pom.xmlhas eight direct dependencies spanning Spring Boot, Spring Security, the MongoDB Java driver, and the JWT library.
That dependency tree is where the story starts.
The Dependency Snapshot — What Was In the pom.xml
Before running anything, here's exactly what the project declared as of the last commit in 2019:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>3.9.1</version>
</dependency>
Eight direct dependencies. All declared at versions that were current in mid-2018 to mid-2019. All untouched since.
The Java compiler target is 1.8 — Java 8, which reached end of life for free Oracle support in January 2019. The project has been running on a deprecated runtime configuration since approximately the month it was committed.
Running the Scan
Setup is straightforward. With Snyk installed and authenticated:
cd mflix
snyk test --all-projects
Snyk reads the pom.xml, resolves the full dependency tree including transitive dependencies, and cross-references every package version against its vulnerability database.
The result appeared in seconds. The dashboard view told the story immediately:
10 Critical · 99 High · 59 Medium · 20 Low
Total: 188 fixable vulnerabilities. Zero with no supported fix.
That last number — zero unfixable — is actually significant. Every single vulnerability Snyk found has a known fix available. I'll come back to what that means for the remediation strategy.
The Finding That Stopped Me Cold
Before getting into the full breakdown, one finding deserves immediate attention.
org.springframework:spring-context@5.0.7.RELEASE — Remote Code Execution. CVSS 9.8. Priority Score 919.
The vulnerability is in spring-beans@5.0.7.RELEASE via CWE-94 — improper control of code generation. An attacker who can reach the application can, under certain conditions, execute arbitrary code on the server.
CVSS 9.8. That's not a theoretical risk category. That's one point below the maximum possible score. That's the kind of finding that, in a production system, triggers an emergency change control process at 11pm on a Friday.
MFlix was never deployed to production. The attack surface was always zero. But the finding is real — the vulnerability exists in the version of spring-beans that ships with spring-context@5.0.7, and it would be exploitable if the application were running and accessible.
This is the finding that "security best practices" in the README was supposed to prevent. It didn't — not because of negligence, but because security best practices in 2019 didn't include the CVE that was disclosed after the code was written.
The Full Breakdown by Dependency
Here's what Snyk found grouped by the dependency it originated from, with priority scores and headline vulnerability types:
Critical Severity (Priority Score 919)
org.springframework:spring-web@5.0.7.RELEASE
11 direct issues, 8 transitive issues. The highest-priority dependency in the scan.
- Remote Code Execution — CWE-94, CVSS 9.8 (via
spring-beans) - Privilege Escalation — CWE-264, CVSS 4.4
- Improper Input Validation — CWE-20, CVSS 8.6
- Reflected File Download — CWE-494, CVSS 8.0
- Denial of Service — CWE-400, CVSS 3.7
org.springframework:spring-context@5.0.7.RELEASE2 direct issues, 11 transitive issues. - Remote Code Execution — CWE-94, CVSS 9.8 (via
spring-beans) - Relative Path Traversal — CWE-23, CVSS 8.2
- Incorrect Authorization — CWE-863, CVSS 8.7
org.springframework.boot:spring-boot-starter-security@2.0.4.RELEASE37 transitive issues. - Authorization Bypass — CWE-285, CVSS 8.2
- Reflected File Download — CWE-494, CVSS 8.0
- Improper Input Validation — CWE-20, CVSS 8.6
org.springframework.boot:spring-boot-starter-web@2.0.3.RELEASE206 transitive issues — the single dependency pulling in the most downstream vulnerabilities. - Insecure Defaults via
tomcat-embed-core@8.5.31— CWE-453, CVSS 9.8 - Deserialization of Untrusted Data via
jackson-databind@2.9.6— CWE-502, CVSS 9.2 - Session Fixation via
tomcat-embed-core— CWE-384, CVSS 3.1 - Cross-Site Scripting via
tomcat-embed-core— CWE-79, CVSS 3.5io.jsonwebtoken:jjwt@0.9.1— Priority Score 889 63 transitive issues, 58 fixable. All fixed in a single upgrade to version 0.12.0. - Deserialization of Untrusted Data via
jackson-databind@2.9.6— CWE-502, CVSS 9.2 - Allocation of Resources Without Limits via
jackson-core— CWE-770, CVSS 8.x ### Critical — Certificate Validation
org.springframework.boot:spring-boot-autoconfigure@2.0.3.RELEASE
- Improper Validation of Certificate with Host Mismatch — CWE-297, CVSS 9.3 This one matters specifically because MFlix handles user authentication. A TLS certificate validation bypass in the autoconfigure layer means a connection claiming to be a trusted service could potentially be impersonated without detection.
High Severity (Priority Score 649)
org.springframework.boot:spring-boot@2.0.4.RELEASE
4 direct issues, 7 transitive.
- Insecure Temporary File — CWE-377, CVSS 7.8
- Incorrect Authorization — CWE-863, CVSS 8.7
org.springframework:spring-core@5.0.7.RELEASE5 direct issues. - Incorrect Authorization — CWE-863, CVSS 8.7
- Improper Case Sensitivity Handling — CWE-178, CVSS 2.3 ### Medium Severity
org.mongodb:mongodb-driver-sync@3.9.1
1 direct issue.
- Man-in-the-Middle — CWE-300, CVSS 6.4 The MongoDB driver finding is particularly relevant for this application. MFlix connects to a MongoDB Atlas cluster. A MitM vulnerability in the driver layer means the connection between the application and the database could potentially be intercepted.
The Transitive Dependency Problem
The number that tells the real story of legacy Java dependency management is this one:
spring-boot-starter-web@2.0.3 — 206 transitive issues.
One line in the pom.xml. One version declaration. 206 downstream vulnerabilities pulled in through the dependency chain, in packages the application never explicitly imported and whose version numbers never appeared in the build file.
This is the fundamental challenge of Software Composition Analysis in Java projects. The pom.xml has eight dependencies. The actual dependency graph has dozens of packages. Each of those packages has its own version, its own CVE history, its own patch cadence.
A developer in 2018 who wrote spring-boot-starter-web@2.0.3 made a single decision. That decision pulled in a specific version of Tomcat, a specific version of Jackson, a specific version of Spring's core libraries — all of which have accumulated vulnerabilities over six years. None of those vulnerabilities were visible at the point of the original decision.
This is why dependency scanning exists. The alternative — manually tracking CVE disclosures across every transitive dependency in your build graph — is not a realistic approach for any team at any scale.
The Exploit Maturity Breakdown
Not all 188 findings carry the same actual risk. Snyk's exploit maturity classification tells a more nuanced story:
| Maturity Level | Count |
|---|---|
| Mature exploits (working exploit code exists) | 8 |
| Proof-of-concept exploits | 43 |
| No known exploit | 137 |
Eight findings have working, publicly available exploit code. These are the ones that require the most urgent remediation — not because the vulnerability is necessarily more severe than others, but because the barrier to exploitation is effectively zero. Anyone with the exploit code and network access to the application could use it.
The 137 findings with no known exploit are real vulnerabilities — they're in the CVE database, they have CVSS scores, Snyk flags them correctly — but the practical attack risk is lower because exploitation requires custom effort rather than running a public tool.
This breakdown becomes critical for article 4, where I'll explain which findings I remediated, which I suppressed, and why the exploit maturity classification was the primary factor in that decision.
What "100% Fixable" Actually Means
One number that surprised me when I first saw the Snyk output: 188 fixable, 0 with no supported fix.
That's unusually clean. In my experience scanning production Java codebases at work, there are almost always some findings in the "no supported fix" category — typically because a vulnerable package has no patched version available, or because the fix requires changes that would break the API the application depends on.
MFlix having 100% fixable findings is partly a function of how old the dependencies are. Six years is a long time. Every major vulnerability that exists in Spring Boot 2.0.x, Spring 5.0.x, and jjwt 0.9.1 has had years to be patched in subsequent versions. The fix exists — I just have to apply it.
The question is how straightforward "applying the fix" actually is. Some of these are minor version bumps. Others are major version upgrades — Snyk recommends going from spring-boot-starter-web@2.0.3 to 2.0.6 for some fixes and all the way to 3.x for others. Major version upgrades in Spring Boot involve breaking API changes that require code modifications, not just version bumps.
That complexity is what articles 4 and 5 are about. The 100% fixability rate is the theoretical ceiling. The practical remediation story is considerably more interesting.
The Irony of the README
I want to return to that line. "Implementing security best practices."
It was accurate in 2019. I used parameterised queries for MongoDB operations. I implemented JWT authentication correctly for the era. I used Spring Security for access control. I followed the MongoDB University course's security guidance.
None of that is what Snyk found. What Snyk found is a different category of security problem entirely — not vulnerabilities in the code I wrote, but vulnerabilities in the dependencies I imported. The distinction matters because it reveals a gap in how most developers think about application security.
When developers think about writing secure code, they think about SQL injection, authentication flows, authorisation checks, input validation. These are code-level concerns. A skilled developer can learn them, apply them consistently, and write code that is largely free of them.
Software composition vulnerabilities are different. They accumulate silently. They appear in packages you didn't write and may never read. They arrive via CVE disclosures months or years after you made your dependency choices. They require an ongoing process — not a one-time skill — to manage.
The "security best practices" that prevent code-level vulnerabilities are largely different from the "security best practices" that prevent composition vulnerabilities. Both matter. Until I ran this scan, I was only thinking about one of them.
What Comes Next
Over the next six articles in this series, I'll document:
Article 2 — Modernising the project structure: wrapping the legacy code in a current Spring Boot shell, what changed, and what had to change before Snyk could even give me a useful remediation path.
Article 3 — The full unfiltered Snyk results: a deeper dive into the most significant findings with full CVE context, what each vulnerability actually enables an attacker to do, and which ones matter most for an application with user authentication and database access.
Article 4 — Why I suppressed some findings and fixed others: the risk assessment framework, the role of exploit maturity in prioritisation, and the findings I made a documented decision to accept.
Article 5 — The remediation work itself: the easy version bumps, the breaking changes that required code modification, and the one dependency upgrade that took four attempts to get right.
Article 6 — Before and after metrics: what changed, how to measure security posture improvement, and what the numbers look like when you present them to an engineering team.
Article 7 — Using AI to assist with remediation: what worked, what didn't, and the difference between AI suggestions and Snyk recommendations on the same vulnerabilities.
The repository is at github.com/pgmpofu/mflix. The pom.xml in the current state is exactly as it was in 2019. The Snyk findings are real. The remediation work is ongoing.
Next article: modernising the project — what a 2019 Spring Boot structure looks like compared to what a current one should look like, and what had to change before the security work could begin in earnest.
Top comments (0)