fix express proxy headers

This commit is contained in:
Zimeng Xiong
2026-01-30 14:26:03 -08:00
parent 4b56d3cfc6
commit d67bd1daf8
3 changed files with 39 additions and 9 deletions
@@ -97,7 +97,6 @@ describe("Issue #38: CSRF with trust proxy settings", () => {
const clientId1 = `${externalProxyIp1}:${userAgent}`;
const token = createCsrfToken(clientId1);
console.log("\n📝 Step 1: Client fetches CSRF token");
console.log(
" X-Forwarded-For:",
`${clientIp1}, ${externalProxyIp1}, 172.17.0.3`,
@@ -111,7 +110,6 @@ describe("Issue #38: CSRF with trust proxy settings", () => {
const clientId2 = `${externalProxyIp2}:${userAgent}`;
console.log("\n📤 Step 2: Client creates drawing with token");
console.log(
" X-Forwarded-For:",
`${clientIp1}, ${externalProxyIp2}, 172.17.0.3`,
+33 -5
View File
@@ -132,8 +132,21 @@ const app = express();
// Trust proxy headers (X-Forwarded-For, X-Real-IP) from nginx
// Required for correct client IP detection when running behind a reverse proxy
// This fixes CSRF token validation failures in Docker/K8s environments
app.set("trust proxy", 1);
// Fix for issue #38: Use 'true' to handle multiple proxy layers (e.g., Traefik, Synology NAS)
// This ensures Express extracts the real client IP from the leftmost X-Forwarded-For value
const trustProxyConfig = process.env.TRUST_PROXY || "true";
const trustProxyValue = trustProxyConfig === "true"
? true
: trustProxyConfig === "false"
? false
: parseInt(trustProxyConfig, 10) || 1;
app.set("trust proxy", trustProxyValue);
if (trustProxyValue === true) {
console.log("[config] trust proxy: enabled (handles multiple proxy layers)");
} else {
console.log(`[config] trust proxy: ${trustProxyValue}`);
}
const httpServer = createServer(app);
const io = new Server(httpServer, {
@@ -330,9 +343,24 @@ app.use((req, res, next) => {
const getClientId = (req: express.Request): string => {
const ip = req.ip || req.connection.remoteAddress || "unknown";
const userAgent = req.headers["user-agent"] || "unknown";
// Create a simple hash for client identification
// In production, you might use a session ID instead
return `${ip}:${userAgent}`.slice(0, 256);
const clientId = `${ip}:${userAgent}`.slice(0, 256);
// Debug logging for CSRF troubleshooting (issue #38)
if (process.env.DEBUG_CSRF === "true") {
console.log("[CSRF DEBUG] getClientId", {
method: req.method,
path: req.path,
ip,
remoteAddress: req.connection.remoteAddress,
"x-forwarded-for": req.headers["x-forwarded-for"],
"x-real-ip": req.headers["x-real-ip"],
userAgent: userAgent.slice(0, 100),
clientIdPreview: clientId.slice(0, 60) + "...",
trustProxySetting: req.app.get("trust proxy"),
});
}
return clientId;
};
// Rate limiter specifically for CSRF token generation to prevent store exhaustion
+6 -2
View File
@@ -569,8 +569,12 @@ const getCsrfSecret = (): Buffer => {
cachedCsrfSecret = crypto.randomBytes(32);
const envLabel = process.env.NODE_ENV ? ` (${process.env.NODE_ENV})` : "";
console.warn(
`[security] CSRF_SECRET is not set${envLabel}. Using an ephemeral per-process secret. ` +
"For horizontal scaling (k8s), set CSRF_SECRET to the same value on all instances."
`[SECURITY WARNING] CSRF_SECRET is not set${envLabel}.\n` +
`Using an ephemeral per-process secret.\n` +
` - Tokens will expire on container restart\n` +
` - Horizontal scaling (k8s) will NOT work\n` +
` - Generate a secret: openssl rand -base64 32\n` +
` - Set environment variable: CSRF_SECRET=<generated-secret>`
);
return cachedCsrfSecret;
};