September 13, 2026
What Closing 9 High-Severity Security Findings Taught Me About Auth, CSRF, and SSRF
Over the past year I worked through two internal security reviews on a production Spring Boot service, closing nine High-severity findingsβ¦

By Md Farazul Haque
2 min read
Over the past year I worked through two internal security reviews on a production Spring Boot service, closing nine High-severity findings along the way. None of this is about one company or one audit. The same three mistakes show up in most session-based web apps, and the fixes generalize cleanly. Here's what actually changed, and why each fix needed more than a code review to be believable.
Where You Put the Auth Token Matters More Than You Think
The original setup stored a JWT in localStorage and attached it as a Bearer token on every request. It worked, and it's an extremely common pattern β until you remember that any XSS on the page can read localStorage and exfiltrate that token. A stolen JWT stays valid for as long as its expiry window allows, sometimes many hours. Moving auth into an httpOnly, Secure, SameSite cookie closes that door: page scripts simply can't read it.
The CSRF You Just Signed Up For
Here's the part that surprises people: Bearer-token auth is naturally immune to CSRF, because a malicious site can't attach an Authorization header on your behalf. Cookies are different β browsers attach them automatically to any request to that domain, cross-origin or not. So the moment you move to cookie-based auth, you've reopened a CSRF hole that Bearer tokens had closed for free. The fix is a double-submit token: a CSRF value the frontend reads from a non-httpOnly cookie and echoes back as a custom header, which the backend verifies matches. Simple in theory β but the only way to actually trust it is to test it through a real cross-origin browser flow, not curl, because SameSite and Secure enforcement is a browser behavior, not a server one.
When Your Own App Blinds the WAF
The more interesting finding was an SSRF path hiding behind a perimeter WAF. The app needed to accept internal or private URLs for legitimate reasons, which meant a blanket WAF rule blocking those would break real functionality. So at some point, the app was changed to mangle the scheme separator in those URL fields before they hit the WAF, then quietly un-mangle it server-side. That satisfied the WAF rule β but it also meant the WAF was no longer meaningfully inspecting anything else in those requests either. A compensating control living entirely outside the app isn't a control if the app itself can blind it.
Verify Like an Attacker, Not Like a Unit Test
The fix wasn't a WAF change β that's owned by a different team with a different risk trade-off β it was app-side validation: reject control characters, oversized input, and any non-http or non-https scheme. That way the endpoint fails safely even if the perimeter doesn't catch it. And the only way I trusted this was actually done was testing it with the real obfuscated transport format and real malicious payloads against a live environment, not synthetic requests against a mocked one.
None of these fixes are exotic. They look obvious in hindsight and are easy to skip in the moment. The pattern that mattered more than any individual fix: don't consider a security finding closed until you've verified it the way an attacker would actually exercise it β real browser, real transport, real edge case β not just the happy path a unit test checks.