What Happens When a React Library Gets a Security Audit
What Happens When a React Library Gets a Security Audit
Refine scored 38/F on the Security Headers & Basics audit. That is the lowest score in our benchmark dataset outside of example/starter apps. But Refine is a well-maintained React meta-framework with 30k+ GitHub stars, backed by a funded company, with active security practices.
So what happened?
The audit did its job (too well)
The Security Headers audit checks 20 things across four categories: Transport Security, Security Headers, Information Exposure, and Basic Hygiene. Here is what Refine's results look like:
Transport Security (30% weight):
- HTTPS enforced: fail
- HSTS enabled: fail
- Secure cookie flag: fail
- SameSite cookie attribute: fail
Security Headers (30% weight):
- CSP present: fail
- CSP no unsafe-inline: fail
- X-Frame-Options: fail
- X-Content-Type-Options: fail
- Referrer-Policy: fail
- Permissions-Policy: fail
Basic Hygiene (15% weight):
- .env in .gitignore: pass
- No hardcoded secrets: pass
- Lockfile present: pass
- security.txt: fail
Every transport and headers check fails. Every single one. And the audit is correct: there is no HTTPS enforcement, no HSTS, no CSP, no X-Frame-Options. These headers do not exist in the Refine codebase.
They do not exist because Refine does not serve HTTP responses. It is a client-side library. It runs inside a browser. It does not have a server. It does not set headers because it does not send responses.
The shared responsibility problem
This is the same issue that hits every client-side framework, component library, and frontend toolkit. The security boundary looks like this:
[Refine / React / Vue / Svelte] --> Client-side library
|
v
[Next.js / Express / nginx] --> Server (sets headers)
|
v
[Vercel / AWS / your VPS] --> Infrastructure (TLS, HSTS)
Refine operates at the top layer. It builds UI. The server and infrastructure layers below it are responsible for transport security and response headers. When you build an app with Refine and deploy it on Next.js on Vercel, the headers come from Next.js and Vercel — not from Refine.
The audit does not know this. It looks at the codebase, sees no header configuration, and marks every check as failed. The score is technically accurate. The implication — that Refine is insecure — is not.
When should client-side libraries care about headers?
Almost never for transport-level headers. But there are a few checks in the audit that are legitimately relevant to client-side code:
Information Exposure matters everywhere. A client-side library should not ship source maps to production, should not expose stack traces to end users, and should not leak version information unnecessarily. Refine passes some of these.
Basic Hygiene is universal. Every project should have its .env in .gitignore, should avoid hardcoded secrets, and should keep a lockfile. Refine passes all of these.
CSP compatibility is relevant even for libraries. If a library injects inline scripts or styles, it forces downstream applications to use unsafe-inline in their CSP, which defeats a significant part of the protection CSP provides. Libraries should test that they work with strict CSP policies even though they do not set CSP themselves.
So the real Refine security posture is closer to: Basic Hygiene looks solid, Information Exposure is reasonable, and Transport/Headers are not applicable. That is a meaningfully different story from "38/F."
What the score means vs. what it does not
The 38/F means: "If you deploy this codebase as-is, without any server-side header configuration, your users are exposed to clickjacking, protocol downgrade attacks, MIME sniffing, and cross-site data leakage."
That is true. It is also true of literally every React component library, every npm package, and every client-side framework ever written. React itself would score similarly. So would Tailwind CSS.
The score does not mean:
- Refine has security vulnerabilities
- Refine is poorly maintained
- You should not use Refine
- Refine is less secure than projects scoring 80+
It means Refine is a different kind of project than the audit was designed for.
The lesson for audit consumers
When you see a low score, check the project type before drawing conclusions. Ask:
- Does this project serve HTTP responses? If no, Transport Security and Security Headers categories are measuring the wrong layer.
- Is this a library or an application? Libraries delegate many security responsibilities to the consuming application.
- Which categories are failing? A project that fails Transport but passes Basic Hygiene is in a fundamentally different situation than one that fails both.
We are working on better handling for this in AuditBuffet — potentially flagging client-side-only projects and adjusting which checks are considered applicable. In the scoring system, checks marked as "skip" (not applicable) are excluded from both the numerator and denominator, so the score only reflects checks that are actually relevant.
Until then, treat audit scores as a starting point for investigation, not a final verdict. An F that comes entirely from inapplicable checks is not the same as an F that comes from real failures.
What app developers should do
If you are building an application with Refine (or any client-side framework), the security headers are your responsibility. The framework gives you the UI. You provide the server. The server provides the headers.
For a typical Refine + Vite + Express setup:
// express server
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;");
next();
});
Run the Security Headers audit against your deployed application, not against the library you used to build it. That is where the score matters.