Skip to content

Commit 4552f78

Browse files
committed
Trust proxy headers only from allowlist
1 parent c0ec16e commit 4552f78

File tree

2 files changed

+64
-13
lines changed

2 files changed

+64
-13
lines changed

server/server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ console.log('----------------------------------------');
107107

108108
const app = express();
109109
const port = process.env.PORT || 3200;
110-
app.set('trust proxy', true);
110+
app.set('trust proxy', false);
111111
const parseIpList = (value) => {
112112
if (!value || typeof value !== 'string') return new Set();
113113
return new Set(

server/utils.js

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,61 @@
11
import rateLimit, { ipKeyGenerator } from 'express-rate-limit';
22
import fs from 'fs';
3+
import net from 'node:net';
34
import { authCache } from './server.js';
45

56
const maxLimitReq = parseInt(process.env.RATE_LIMIT) || 50;
67
const bannedIpsFile = './banned-ips.log';
8+
const trustedProxyCidrsRaw = String(process.env.TRUSTED_PROXY_CIDRS || '').trim();
9+
const trustedProxyAllowlist = new net.BlockList();
10+
let trustedProxyRulesLoaded = false;
711

812
function sanitizeIpValue(value) {
913
if (typeof value !== 'string') return '';
10-
const cleaned = value.replace(/[\r\n\t]/g, '').trim();
14+
let cleaned = value.replace(/[\r\n\t]/g, '').trim();
1115
if (!cleaned) return '';
12-
return /^[A-Fa-f0-9:.]+$/.test(cleaned) ? cleaned : '';
16+
if (cleaned.startsWith('::ffff:')) {
17+
cleaned = cleaned.slice(7);
18+
}
19+
if (cleaned.startsWith('[') && cleaned.endsWith(']')) {
20+
cleaned = cleaned.slice(1, -1);
21+
}
22+
if (!cleaned) return '';
23+
return net.isIP(cleaned) ? cleaned : '';
24+
}
25+
26+
function loadTrustedProxyRules() {
27+
if (trustedProxyRulesLoaded) return;
28+
trustedProxyRulesLoaded = true;
29+
if (!trustedProxyCidrsRaw) return;
30+
31+
const rules = trustedProxyCidrsRaw
32+
.split(/[\s,]+/)
33+
.map((entry) => entry.trim())
34+
.filter(Boolean);
35+
36+
for (const rule of rules) {
37+
const [address, prefix] = rule.split('/');
38+
const sanitizedAddress = sanitizeIpValue(address);
39+
const prefixNum = Number(prefix);
40+
const ipVersion = net.isIP(sanitizedAddress);
41+
42+
if (!sanitizedAddress || Number.isNaN(prefixNum) || !Number.isInteger(prefixNum) || !ipVersion) {
43+
console.warn(`Skipping invalid TRUSTED_PROXY_CIDRS entry: ${rule}`);
44+
continue;
45+
}
46+
47+
const type = ipVersion === 4 ? 'ipv4' : 'ipv6';
48+
trustedProxyAllowlist.addSubnet(sanitizedAddress, prefixNum, type);
49+
}
50+
}
51+
52+
function isTrustedProxyIp(ip) {
53+
loadTrustedProxyRules();
54+
if (!trustedProxyCidrsRaw) return false;
55+
const sanitizedIp = sanitizeIpValue(ip);
56+
if (!sanitizedIp) return false;
57+
const type = net.isIP(sanitizedIp) === 4 ? 'ipv4' : 'ipv6';
58+
return trustedProxyAllowlist.check(sanitizedIp, type);
1359
}
1460

1561
export function addIpToBannedList(ip) {
@@ -32,19 +78,24 @@ export function addIpToBannedList(ip) {
3278
}
3379

3480
export function getClientIp(req) {
35-
const cfConnectingIp = req.headers['cf-connecting-ip'];
36-
if (cfConnectingIp) {
37-
const ip = sanitizeIpValue(cfConnectingIp.split(',')[0]);
38-
if (ip) return ip;
39-
}
81+
const peerIp = sanitizeIpValue(req.socket?.remoteAddress);
82+
const trustForwardedHeaders = isTrustedProxyIp(peerIp);
83+
84+
if (trustForwardedHeaders) {
85+
const cfConnectingIp = req.headers['cf-connecting-ip'];
86+
if (cfConnectingIp) {
87+
const ip = sanitizeIpValue(String(cfConnectingIp).split(',')[0]);
88+
if (ip) return ip;
89+
}
4090

41-
const forwardedFor = req.headers['x-forwarded-for'];
42-
if (forwardedFor) {
43-
const ip = sanitizeIpValue(forwardedFor.split(',')[0]);
44-
if (ip) return ip;
91+
const forwardedFor = req.headers['x-forwarded-for'];
92+
if (forwardedFor) {
93+
const ip = sanitizeIpValue(String(forwardedFor).split(',')[0]);
94+
if (ip) return ip;
95+
}
4596
}
4697

47-
return sanitizeIpValue(req.ip) || req.ip;
98+
return sanitizeIpValue(req.ip) || peerIp || 'unknown';
4899
}
49100

50101
function getRateLimitKey(req) {

0 commit comments

Comments
 (0)