Header Injection in Express with Firestore
Header Injection in Express with Firestore — how this specific combination creates or exposes the vulnerability
Header Injection occurs when user-controlled data is reflected into HTTP response headers without validation or encoding. In an Express application that interacts with Cloud Firestore, this typically happens when values from Firestore documents or query parameters are passed directly into headers such as Location, X-Download-Options, or custom headers. Because Firestore documents can contain user-supplied or attacker-influenced fields (for example, user profiles, redirect URLs, or metadata), using these values in headers introduces injection risks.
Consider an Express route that retrieves a redirect URL from a Firestore document and sets it in the Location header:
const {Firestore} = require('@google-cloud/firestore');
const express = require('express');
const app = express();
const db = new Firestore();
app.get('/redirect/:docId', async (req, res) => {
const doc = await db.doc(`redirects/${req.params.docId}`).get();
if (!doc.exists) {
return res.status(404).send('Not found');
}
const url = doc.data().url;
res.set('Location', url);
res.status(302).send('Redirecting...');
});
If the url field stored in the Firestore document contains a newline or carriage return (e.g., https://example.com\r\nSet-Cookie: session=attacker), the res.set('Location', url) call can inadvertently inject additional headers. Express does not inherently sanitize header values, and Firestore does not enforce a restricted character set for string fields, enabling header splitting. This can lead to cache poisoning, session fixation, or HTTP response splitting, which may allow an attacker to inject malicious content or bypass intended routing.
Header injection in this context also intersects with insecure deserialization and SSRF-like behaviors when the injected header causes the client to interact with internal resources. For example, an attacker-controlled X-Forwarded-Host header might influence URL generation in the application or proxy layers. Because Firestore is often used to store configuration or redirect rules, developers may trust its contents more than user input, inadvertently widening the attack surface.
Another scenario involves custom headers used for internal routing or feature flags. If an Express route copies Firestore field values into response headers such as X-Feature-Flag, an attacker who can write to Firestore (via compromised client SDK rules or misconfigured security rules) can inject arbitrary header values. While browsers may mitigate some effects, non-browser clients may execute or trust the injected headers, leading to privilege escalation or information leakage.
Because Firestore rules cannot validate header-safe characters, the responsibility falls to the Express application to sanitize and validate any data from Firestore before it reaches headers. Header injection in this stack is not a single vulnerability but a pattern of trust boundaries violated: data originating from a managed database is assumed safe for protocol-level use, which is rarely true without explicit validation.
Firestore-Specific Remediation in Express — concrete code fixes
To prevent Header Injection when using Express with Firestore, you must validate and sanitize any Firestore document data that is used in HTTP headers. This includes user-controlled fields as well as fields that may be inadvertently writable by clients. Below are concrete, Firestore-aware remediation patterns for Express.
1. Validate and restrict header values
Never set headers directly from Firestore fields. Instead, enforce a strict allowlist for expected values (e.g., known redirect domains) or sanitize newlines and control characters. For URLs, use a URL parser and enforce a strict schema and host allowlist:
const {Firestore} = require('@google-cloud/firestore');
const express = require('express');
const app = express();
const db = new Firestore();
const ALLOWED_HOSTS = new Set(['example.com', 'cdn.example.com']);
const url = require('url');
app.get('/redirect/:docId', async (req, res) => {
const doc = await db.doc(`redirects/${req.params.docId}`).get();
if (!doc.exists) {
return res.status(404).send('Not found');
}
const rawUrl = doc.data().url;
let parsed;
try {
parsed = new url.URL(rawUrl);
} catch (err) {
return res.status(400).send('Invalid URL');
}
if (!ALLOWED_HOSTS.has(parsed.hostname)) {
return res.status(403).send('Forbidden host');
}
// Remove any newline/carriage return characters
const sanitized = rawUrl.replace(/[\r\n]+/g, '');
res.set('Location', sanitized);
res.status(302).send('Redirecting...');
});
2. Use a header-safe transformation layer
If you must pass Firestore values into headers, normalize them to a safe format. For example, for metadata headers, remove non-ASCII and control characters and avoid using values that resemble header syntax:
function headerSafe(value) {
if (typeof value !== 'string') return '';
return value.replace(/[^\x20-\x7e\x80-\xff]/g, '').replace(/[\r\n]+/g, ' ').trim();
}
app.get('/profile/:userId', async (req, res) => {
const doc = await db.doc(`users/${req.params.userId}`).get();
if (!doc.exists) {
return res.status(404).send('Not found');
}
const displayName = headerSafe(doc.data().displayName);
res.set('X-Display-Name', displayName);
res.json(doc.data());
});
3. Enforce Firestore security rules to limit header-relevant fields
While rules cannot prevent header injection directly, you can restrict which clients can write fields that are used in headers. For example, prevent user-writable fields from containing newline characters by using Firestore validation rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /redirects/{docId} {
allow read: if true;
allow write: if request.resource.data.url is string
&& request.resource.data.url.size() < 2048
&& !request.resource.data.url.matches('.*[\r\n].*');
}
}
}
These rules reduce the likelihood that a malicious document write will contain newline-based injection payloads. Combined with Express-side sanitization, this defense-in-depth approach mitigates Header Injection risks in the Express + Firestore stack.