WhollySoftware
Back to blogWeb

Web Security Basics That Still Get Missed: CSRF, XSS, and CSP

Wholly Software TeamSeptember 14, 20246 min read
Web Security Basics That Still Get Missed: CSRF, XSS, and CSP

A pre-launch security audit on a fintech client's React and Express application flagged three issues, and what struck us was how basic all three were despite the app being built on frameworks that supposedly protect against them by default. The first was a CSRF vulnerability on a state-changing endpoint that had been built as a plain form POST outside the app's main API client, which meant it bypassed the CSRF token middleware applied everywhere else — a gap introduced by one endpoint not following the established pattern, not a framework failure.

The second was a stored XSS vulnerability in a user bio field that got rendered with dangerouslySetInnerHTML to support basic formatting like bold and links. React escapes content by default specifically to prevent this class of bug, and the vulnerability existed only because a developer opted out of that protection to support rich text, without sanitizing the input first. We fixed it with DOMPurify sanitizing the HTML server-side before storage, on the principle that you sanitize at the point of storage, not just at render time, since the same data might get rendered somewhere else later without the same escaping.

The third was a missing Content-Security-Policy header entirely. CSP is easy to skip because a site functions completely normally without one — there's no visible bug, just a missing layer of defense that would have limited the damage if the XSS issue above had made it to production. We added a CSP that allowlists script sources explicitly rather than using unsafe-inline, which took some iteration since a few third-party analytics scripts needed nonce-based allowlisting to keep working under a strict policy.

None of these three required exotic knowledge to fix, and none of them were things the frameworks failed to prevent — Express doesn't ship CSRF protection built in, and dangerouslySetInnerHTML exists precisely because React can't sanitize content on your behalf when you've explicitly asked it to trust you. The common thread was a single endpoint, field, or header falling outside the app's established security defaults, which is exactly the kind of gap that a security audit exists to catch and that day-to-day development, moving fast toward a launch date, tends to miss.

We've since added CSRF, XSS, and CSP checks to our own pre-launch checklist rather than relying solely on an external audit to catch them, specifically because all three are cheap to verify systematically and expensive to discover after a breach.

SecurityCSRFXSSCSP
Web Security Basics That Still Get Missed: CSRF, XSS, and CSP — Wholly Software