HTTP Header Injection in Vibe Coded Apps
How to find and fix CRLF injection and response splitting in redirect endpoints
HTTP header injection happens when attackers inject CRLF characters (\r\n) into HTTP response headers. This enables cache poisoning, XSS, and session attacks.
Classified as CWE-113. Always sanitize user input before including in headers.
Source: OWASP Top 10 (2021) | CWE-113 | PortSwigger
What is HTTP header injection?
HTTP header injection is a vulnerability where attackers insert CRLF characters (carriage return \r and line feed \n) into HTTP response headers.
Since HTTP uses these characters to separate headers, injecting them lets attackers add malicious headers or split the response entirely.
Think of it like inserting fake pages into a sealed envelope by sneaking in paper clips that force it open. The envelope (HTTP response) has a specific structure, and CRLF injection breaks that structure to insert attacker-controlled content.
According to OWASP A03:2021 Injection, this vulnerability enables cache poisoning, XSS, session fixation, and phishing attacks.
It's particularly common in vibe coded redirect endpoints where AI tools generate res.redirect(req.query.returnUrl) without sanitization.
How does CRLF injection enable response splitting?
HTTP headers follow a strict format: each header is separated by \r\n, and the body starts after \r\n\r\n.
When attackers inject these sequences, they manipulate the response structure.
HTTP Response Format
HTTP/1.1 302 Found\r\n
Location: /dashboard\r\n
Set-Cookie: session=abc123\r\n
\r\n
<body starts here>\r\nseparates each header\r\n\r\n(double CRLF) separates headers from body- Injecting these characters breaks the structure
Single CRLF: Header Injection
Inject one CRLF to add a new header:
// Attack URL:
/redirect?url=/page%0d%0aSet-Cookie:%20session=attacker
// Results in:
Location: /page\r\n
Set-Cookie: session=attacker\r\n // INJECTED!
...rest of responseThe attacker injects a Set-Cookie header to fix the session to their value.
Double CRLF: Response Splitting
Inject two CRLFs to start the response body early:
// Attack URL:
/redirect?url=/page%0d%0a%0d%0a<script>alert(document.cookie)</script>
// Results in:
Location: /page\r\n
\r\n
<script>alert(document.cookie)</script> // INJECTED BODY!The double CRLF terminates headers, and the script executes in the browser.
Why do AI tools generate vulnerable header code?
AI tools generate header injection vulnerabilities because they prioritize working redirects over security. When you ask for "redirect after login," the AI generates the simplest solution.
Common AI-generated vulnerable patterns
// What Cursor typically generates (VULNERABLE)
app.get('/login', (req, res) => {
const { username, password } = req.body
// ... authenticate user ...
const returnUrl = req.query.returnUrl || '/'
res.redirect(returnUrl) // DANGEROUS! No validation
})
// What Bolt generates for quick auth (VULNERABLE)
app.get('/callback', (req, res) => {
const next = req.query.next || '/dashboard'
res.redirect(next) // DANGEROUS! Direct user input
})
// What Claude Code generates for custom headers (VULNERABLE)
app.get('/track', (req, res) => {
const referrer = req.query.ref || 'direct'
res.setHeader('X-Referrer', referrer) // DANGEROUS!
res.json({ tracked: true })
})Where AI creates these vulnerabilities:
- Redirect endpoints:
/redirect?url=...patterns without URL validation - Login return URLs:
/login?returnUrl=...after authentication - OAuth callbacks: Redirect parameters in auth flows
- Custom headers: X-Forwarded-Host, X-Referrer from user input
- Tracking pixels: URL/email tracking with user-controlled values
What could happen with header injection?
Header injection enables several serious attacks, from session hijacking to cache poisoning that affects all users.
- Cache Poisoning: Inject headers to cache a malicious response for all users. Response queue poisoning can make this critical.
- Session Fixation: Inject
Set-Cookiewith attacker's session ID, then log in as the victim. - XSS via Response Splitting: Terminate headers early and inject JavaScript that executes in browsers.
- Phishing: Inject
Locationheader to redirect users to phishing sites. - Real-world example: Craft CMS X-Forwarded-Host injection enabled password reset email poisoning.
How do I detect header injection?
Detect header injection by searching for code that includes user input in redirect URLs or custom headers without sanitization.
// Direct redirect from query params (DANGEROUS)
res.redirect(req.query.returnUrl)
res.redirect(req.query.next)
redirect(searchParams.get('returnTo'))
// Custom headers from user input (DANGEROUS)
res.setHeader('X-Custom', req.query.value)
res.setHeader('Location', userProvidedUrl)
// String concatenation in headers (DANGEROUS)
'Location: ' + userInput
`X-Referrer: ${req.query.ref}`
// Regex patterns to find these:
// res\.redirect\s*\(\s*req\.(query|params|body)
// res\.setHeader\s*\([^,]+,\s*req\.(query|params|body)
// redirect\s*\(\s*searchParamsQuick manual test: Send %0d%0aX-Injected:%20test in a redirect parameter. If the response includes X-Injected: test as a header, you have header injection.
Don't want to test manually?
Scan your redirect endpointsHow do I fix header injection?
Fix header injection by sanitizing all user input before including in headers. Remove control characters and validate redirect URLs against an allowlist.
AI Fix Prompt
Copy this prompt into Cursor, Claude Code, or Bolt to automatically fix header injection in your codebase:
Manual Fix - Express
// Direct redirect from query - DANGEROUS
app.get('/login', (req, res) => {
const { username, password } = req.body
// ... authenticate user ...
const returnUrl = req.query.returnUrl || '/'
res.redirect(returnUrl) // No validation!
})
// Attack: /login?returnUrl=/%0d%0aSet-Cookie:%20session=evil
// Result: Injects Set-Cookie header// Safe redirect with validation - SECURE
const ALLOWED_PATHS = ['/', '/dashboard', '/profile']
function safeRedirect(res, url) {
// Remove control characters
const sanitized = (url || '/').replace(/[\x00-\x1F\x7F]/g, '')
// Validate: relative path in allowlist
if (!sanitized.startsWith('/')) return res.redirect('/')
const path = sanitized.split('?')[0]
if (!ALLOWED_PATHS.includes(path)) return res.redirect('/')
res.redirect(sanitized)
}
app.get('/login', (req, res) => {
// ... authenticate ...
safeRedirect(res, req.query.returnUrl)
})Manual Fix - Next.js
// Server action with unsanitized redirect
'use server'
import { redirect } from 'next/navigation'
export async function loginAction(formData: FormData) {
const returnTo = formData.get('returnTo') as string
// ... authenticate ...
redirect(returnTo) // No validation!
}// Server action with sanitization - SECURE
'use server'
import { redirect } from 'next/navigation'
const ALLOWED = ['/dashboard', '/profile', '/settings']
function sanitize(path: string): string {
return path.replace(/[\x00-\x1F\x7F]/g, '')
}
export async function loginAction(formData: FormData) {
const returnTo = formData.get('returnTo') as string || '/dashboard'
const safe = sanitize(returnTo)
if (!safe.startsWith('/') || !ALLOWED.some(p => safe.startsWith(p))) {
redirect('/dashboard')
}
redirect(safe)
}Manual Fix - Python/Flask
# Direct redirect from args - DANGEROUS
from flask import request, redirect
@app.route('/login', methods=['POST'])
def login():
# ... authenticate ...
return_url = request.args.get('returnUrl', '/')
return redirect(return_url) # No validation!# Sanitized redirect - SECURE
import re
from flask import request, redirect
ALLOWED_PATHS = ['/', '/dashboard', '/profile']
def sanitize_header(value):
if not isinstance(value, str):
return ''
return re.sub(r'[\x00-\x1F\x7F]', '', value)
@app.route('/login', methods=['POST'])
def login():
# ... authenticate ...
return_url = request.args.get('returnUrl', '/')
safe_url = sanitize_header(return_url)
if not safe_url.startswith('/') or safe_url not in ALLOWED_PATHS:
safe_url = '/'
return redirect(safe_url)Prevention methods by priority
Strip Control Characters
Remove all ASCII control characters (0-31, 127) from user input before using in headers.
value.replace(/[\x00-\x1F\x7F]/g, '')Allowlist Redirect Paths
Only allow redirects to a predefined list of safe paths. Reject anything else.
ALLOWED_PATHS.includes(path)Require Relative Paths
Ensure redirect URLs start with "/" to prevent open redirects.
url.startsWith('/')Use Framework Functions
Express res.redirect() and Next.js redirect() provide some sanitization, but still validate input.
// Still validate even with frameworkFrequently asked questions
What is HTTP header injection?
HTTP header injection is a vulnerability where attackers inject CRLF characters (carriage return and line feed: \r\n) into HTTP response headers. This allows them to add malicious headers, split responses, or inject content into the response body. Classified as CWE-113, it enables cache poisoning, XSS, session fixation, and phishing attacks when user input is included in response headers without sanitization.
What is CRLF injection?
CRLF injection exploits the way HTTP uses carriage return (\r) and line feed (\n) characters to separate headers. By injecting %0d%0a (URL-encoded CRLF), attackers can terminate one header and start another. A double CRLF (%0d%0a%0d%0a) terminates all headers and starts the response body, enabling script injection. Always strip control characters from user input before using in headers.
How does HTTP response splitting work?
HTTP response splitting works by injecting two CRLF sequences (\r\n\r\n) to prematurely end the HTTP headers and start the response body. An attacker sends: /redirect?url=page%0d%0a%0d%0a<script>alert(1)</script>. The server creates a Location header that terminates early, and the script becomes part of the response body that executes in the browser.
Why are CRLF characters dangerous in HTTP headers?
HTTP protocol uses CRLF (\r\n) to delimit headers and a double CRLF to separate headers from the body. If attackers inject these characters, they can add arbitrary headers like Set-Cookie to hijack sessions, or inject JavaScript into the response body. Modern browsers provide some protection, but server-side sanitization is still required to prevent cache poisoning and other server-side attacks.
How do I prevent header injection in Node.js?
Prevent header injection in Node.js by sanitizing all user input before including in headers. Remove control characters: value.replace(/[\x00-\x1F\x7F]/g, ""). For redirects, validate URLs against an allowlist of paths and ensure they start with "/". Express res.redirect() provides some sanitization, but always validate redirect URLs to prevent open redirects combined with header injection.
Related content
Scan your code for header injection
Check your redirect endpoints and custom headers for CRLF injection vulnerabilities.
Try Vibeship Scanner