CRLF Injection in React.js—A Practical, Developer-First Guide
If you’re shipping React apps that talk to APIs, CRLF Injection is a risk worth eliminating early. While most modern browsers and frameworks guard against raw \r\n
(carriage return + line feed) in HTTP headers, vulnerable backends, proxies, and edge cases still let malicious input trigger HTTP response splitting, header injection, or log forging. This guide shows how CRLF Injection in React.js typically happens, how React code can unintentionally become the delivery vehicle, and the best 7 fixes with code you can drop into production.
TL;DR: Treat all user-controlled strings as untrusted, never reflect them into headers, aggressively strip
\r
and\n
server-side, and verify with automated tests and scanners.
What is CRLF Injection (and why React devs should care)?
CRLF Injection occurs when an attacker can smuggle %0d%0a
(i.e., \r\n
) into places where the server or an intermediary interprets it as a new header or new response line. Even if your React front end “only” passes data along, the app can amplify risk if it forwards unsanitized input in URLs, headers, or cookies that the backend echoes.
Key impacts:
- HTTP response splitting: Add headers like
Set-Cookie: admin=true
or even craft a second response segment. - Header injection: Break logging/analytics headers and poison caches.
- Log forging: Insert fake lines into server logs, hiding malicious activity.
- Security header bypass: Weaken CSP/HSTS if intermediaries mistakenly process injected headers.
Throughout this article, we’ll naturally use the main keyword CRLF Injection in React.js (and related phrases like “HTTP response splitting” and “header injection”) to help you (and search engines) find the right content—without stuffing.
Quick Demo: How the “vector” reaches the backend
In a React app, you might read user input and include it in an API call:
// ❌ anti-pattern: do not put user input into headers
async function saveProfile(note) {
// Many browsers disallow CR/LF in header values and will throw,
// but never rely on that. Some proxies or custom stacks may still mishandle it.
await fetch("/api/profile", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Note": note // <--- untrusted, could contain %0d%0a
},
body: JSON.stringify({ note })
});
}
Even though modern Fetch prevents raw \r\n
in header values, do not funnel user input into headers. Instead, keep untrusted data in the body, and sanitize server-side.
Safer pattern:
// ✅ safer: put untrusted input only in the JSON body
async function saveProfileSafe(note) {
await fetch("/api/profile", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ note })
});
}
Related reading from our sites
- Prevent RCE in WP: Stop RCE Exploits in WordPress (helpful if your React front end talks to a WordPress backend).
- From our Cybersrely blog:
Free Security Tool Homepage
Screenshot of our free Website Vulnerability Scanner tool UI
The Best 7 Ways to Fix CRLF Injection in React.js
1) Keep untrusted data out of headers entirely
Rule of thumb: In React, never build headers with user input.
// ❌ bad: dynamic header value from user
const headers = { "X-Tag": userProvidedTag }; // avoid
// ✅ good:
const headers = { "Content-Type": "application/json" };
const body = JSON.stringify({ tag: userProvidedTag }); // use body instead
Why: Even if the browser blocks CR/LF, upstream services, proxy rewrites, or non-standard stacks could mishandle it.
2) Validate & strip CR/LF on the server
Regardless of what React does, sanitize server-side. Here’s an Express snippet:
// middleware/sanitize.js
function stripCRLF(value = "") {
if (typeof value !== "string") return value;
return value.replace(/[\r\n]/g, "");
}
export function sanitizeBody(req, _res, next) {
function cleanse(obj) {
if (!obj || typeof obj !== "object") return;
for (const k of Object.keys(obj)) {
if (typeof obj[k] === "string") obj[k] = stripCRLF(obj[k]);
else if (typeof obj[k] === "object") cleanse(obj[k]);
}
}
cleanse(req.body);
cleanse(req.params);
cleanse(req.query);
next();
}
// server.js
import express from "express";
import helmet from "helmet";
import { sanitizeBody } from "./middleware/sanitize.js";
const app = express();
app.use(helmet()); // adds standard security headers
app.use(express.json());
app.use(sanitizeBody);
app.post("/api/profile", (req, res) => {
const { note } = req.body; // already stripped of CR/LF
res.json({ ok: true, stored: note });
});
app.listen(3000);
Pro tip: Even if your runtime rejects CR/LF in headers, remove them anyway so future refactors don’t reintroduce risk.
3) Never reflect user input into response headers
The fastest way to trigger response splitting on misconfigured stacks is to echo untrusted input in headers:
// ❌ DON'T
app.get("/export", (req, res) => {
const filename = req.query.filename; // untrusted
res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
res.send("file-bytes");
});
// ✅ DO
import contentDisposition from "content-disposition";
app.get("/export", (req, res) => {
const filename = (req.query.filename || "export.txt").toString();
// library validates and quotes; still strip CR/LF first
const safe = filename.replace(/[\r\n]/g, "");
res.setHeader("Content-Disposition", contentDisposition(safe));
res.send("file-bytes");
});
4) Encode user input in URLs & keep it in the query/body
Use encodeURIComponent
in React when interpolating untrusted strings into URLs:
function buildSearchUrl(term) {
const q = encodeURIComponent(term);
return `/api/search?q=${q}`; // backend reads from query safely
}
Important: URL-encoding does not “fix” CRLF Injection in headers, but it does prevent accidental raw control characters from leaking into transport and keeps dangerous sequences visible to your server-side validation.
5) Add a serverwide CR/LF guardrail
Create a helper to set headers safely and ban CR/LF everywhere:
// utils/safeHeaders.js
export function setSafeHeader(res, name, value) {
if (/[^\t\x20-\x7e]/.test(value)) {
// reject any non-printable ASCII (except tab/space)
throw new Error("Unsafe header value");
}
// also explicitly strip CR/LF just in case
res.setHeader(name, value.replace(/[\r\n]/g, ""));
}
// usage
setSafeHeader(res, "X-Env", process.env.NODE_ENV || "prod");
6) Harden your edge/proxy (Nginx)
Sanitize or drop dangerous characters at the proxy layer:
# Drop CR/LF in args that might travel to upstreams
map $arg_note $safe_note {
default "";
"~[\r\n]" "";
"~.+ " $arg_note; # pass through simple values
}
location /api/profile {
proxy_set_header X-Note $safe_note; # or, better: don't set at all
proxy_pass http://app_upstream;
}
Or, better yet, do not set user-controlled headers at the proxy. Keep them in the body.
7) Test it: unit, integration, and curl probes
Automate prevention:
// __tests__/sanitize.test.js
import { stripCRLF } from "../middleware/sanitize.js";
test("strips CR and LF", () => {
expect(stripCRLF("hello\r\nworld")).toBe("helloworld");
expect(stripCRLF("line1\nline2")).toBe("line1line2");
});
Use curl
to probe header handling (from a test machine, not a browser):
# Try to smuggle CR/LF into a custom header
curl -v -H $'X-Note: test\r\nSet-Cookie: injected=1' https://your-api.test/path
Backends should reject this outright or normalize it safely. Your CI can run these probes against non-prod environments.
Sample Vulnerability Report
Sample assessment report generated by our free tool to check Website Vulnerability
Full React/Express Example: Safe flow end-to-end
React component:
import { useState } from "react";
export default function NoteForm() {
const [note, setNote] = useState("");
async function submit(e) {
e.preventDefault();
const res = await fetch("/api/profile", {
method: "POST",
headers: { "Content-Type": "application/json" },
// keep untrusted input in the body
body: JSON.stringify({ note })
});
const data = await res.json();
alert(`Stored: ${data.stored}`);
}
return (
<form onSubmit={submit}>
<label>
Note
<input
value={note}
onChange={(e) => setNote(e.target.value)}
placeholder="Write something safe"
/>
</label>
<button type="submit">Save</button>
</form>
);
}
Express backend (sanitizing middleware + safe header helper):
import express from "express";
import helmet from "helmet";
const app = express();
app.use(helmet());
app.use(express.json());
const stripCRLF = (v = "") =>
typeof v === "string" ? v.replace(/[\r\n]/g, "") : v;
app.use((req, _res, next) => {
// sanitize body/query/params
for (const bag of [req.body, req.query, req.params]) {
if (bag && typeof bag === "object") {
for (const k of Object.keys(bag)) {
if (typeof bag[k] === "string") bag[k] = stripCRLF(bag[k]);
}
}
}
next();
});
function setSafeHeader(res, name, value) {
if (typeof value !== "string") value = String(value);
if (/[^\t\x20-\x7e]/.test(value)) throw new Error("Unsafe header value");
res.setHeader(name, stripCRLF(value));
}
app.post("/api/profile", (req, res) => {
const { note = "" } = req.body;
// never echo user input in headers:
setSafeHeader(res, "X-App", "profiles");
res.json({ ok: true, stored: note });
});
app.listen(3000);
Security checklist for React teams
- Use body payloads for untrusted data; never put user strings in headers.
- Strip CR/LF server-side from all inbound strings.
- Never reflect user input into
Location
,Content-Disposition
,Set-Cookie
, or custom headers. - Deploy Helmet (Node) and strong CSP/HSTS to reduce blast radius.
- Add proxy-layer guards (Nginx) to drop control characters early.
- Write tests to keep regressions from sneaking in.
- Run a scan with our free tool (see images above) to catch CRLF Injection in React.js patterns.
Services & Help
Managed IT Services (Pentest Testing Corp)
Need help hardening configs, CI/CD, and proxies while we support your day-to-day IT?
👉 Managed IT Services
AI Application Cybersecurity
Secure AI/ML apps, prompt flows, vector stores, and model endpoints.
👉 AI Application Cybersecurity
Offer Cybersecurity Service to Your Client
Agencies/MSPs: white-label security assessments and remediation.
👉 Offer Cybersecurity Service to Your Client
Talk to Us
Have an urgent concern about CRLF Injection in React.js or header injection?
👉 Contact Us
Final word
Tightening the seams around CRLF Injection in React.js is a small lift with high payoff. Adopt the seven fixes above, scan your site, and you’ll drastically cut the risk of response splitting, header injection, and messy log trails.
P.S. If you maintain a WordPress backend behind your React app, check out Stop RCE Exploits in WordPress for defense-in-depth.
🔐 Frequently Asked Questions (FAQs)
Find answers to commonly asked questions about CRLF Injection in React.js.