Update server.js
Browse files
server.js
CHANGED
|
@@ -8,17 +8,17 @@ const app = express();
|
|
| 8 |
const PORT = 7860;
|
| 9 |
|
| 10 |
// --- 1. CONFIGURACIÓN DE SEGURIDAD PARANOICA ---
|
|
|
|
| 11 |
app.set('trust proxy', 1);
|
| 12 |
app.disable('x-powered-by');
|
| 13 |
app.use(cors());
|
| 14 |
|
| 15 |
app.use((req, res, next) => {
|
| 16 |
-
// Cabeceras estrictas anti-rastreo y anti-inyección
|
| 17 |
res.setHeader('X-Content-Type-Options', 'nosniff');
|
| 18 |
res.setHeader('X-Frame-Options', 'DENY');
|
| 19 |
res.setHeader('X-XSS-Protection', '1; mode=block');
|
| 20 |
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
|
| 21 |
-
res.setHeader('Referrer-Policy', 'no-referrer');
|
| 22 |
res.setHeader('Content-Security-Policy', "default-src 'none'; frame-ancestors 'none';");
|
| 23 |
next();
|
| 24 |
});
|
|
@@ -27,12 +27,10 @@ app.use(express.json({ limit: '50mb' }));
|
|
| 27 |
app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
| 28 |
|
| 29 |
function logError(providerId, reason) {
|
| 30 |
-
// CRÍTICO: No registrar NUNCA fechas exactas al milisegundo, ni IPs, ni el tamaño del prompt.
|
| 31 |
-
// Solo registramos caídas de la infraestructura.
|
| 32 |
console.error(`[SYSTEM] Provider Error: ${providerId} | ${reason}`);
|
| 33 |
}
|
| 34 |
|
| 35 |
-
//
|
| 36 |
function getRandomUserAgent() {
|
| 37 |
const agents = [
|
| 38 |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
@@ -60,19 +58,19 @@ const PROVIDERS = [
|
|
| 60 |
}
|
| 61 |
];
|
| 62 |
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
| 65 |
let currentLoad = { "pollinations-ai": 0, "kepler-cloud": 0, "voids-api": 0 };
|
| 66 |
|
| 67 |
// --- RATE LIMITING LOCAL ---
|
| 68 |
-
// Mantenemos el rate limit por IP para que no te tumben EL SERVIDOR,
|
| 69 |
-
// pero esta IP jamás saldrá de aquí hacia los proveedores.
|
| 70 |
const limiter = rateLimit({
|
| 71 |
windowMs: 60 * 1000,
|
| 72 |
-
max: 50,
|
| 73 |
keyGenerator: (req) => req.ip,
|
| 74 |
message: { error: { message: "Too many requests.", code: 429 } },
|
| 75 |
-
standardHeaders: false,
|
| 76 |
legacyHeaders: false,
|
| 77 |
});
|
| 78 |
|
|
@@ -99,7 +97,7 @@ async function fetchAllModels() {
|
|
| 99 |
method: "GET",
|
| 100 |
headers: {
|
| 101 |
"Content-Type": "application/json",
|
| 102 |
-
"User-Agent": getRandomUserAgent()
|
| 103 |
}
|
| 104 |
});
|
| 105 |
if (!resp.ok) return [];
|
|
@@ -136,7 +134,6 @@ async function fetchAllModels() {
|
|
| 136 |
}
|
| 137 |
|
| 138 |
app.get('/health', (req, res) => {
|
| 139 |
-
// Info mínima para no dar detalles de la infraestructura a atacantes
|
| 140 |
res.json({ status: "online" });
|
| 141 |
});
|
| 142 |
|
|
@@ -182,10 +179,11 @@ app.post(['/v1/chat/completions', '/v1/images/generations'], limiter, async (req
|
|
| 182 |
const startTime = Date.now();
|
| 183 |
let responseSent = false;
|
| 184 |
|
| 185 |
-
//
|
| 186 |
-
|
|
|
|
| 187 |
let payload = JSON.stringify(req.body);
|
| 188 |
-
req.body = null;
|
| 189 |
|
| 190 |
while (availableProviders.length > 0 && Date.now() - startTime < QUEUE_TIMEOUT) {
|
| 191 |
let selectedProvider = null;
|
|
@@ -215,13 +213,14 @@ app.post(['/v1/chat/completions', '/v1/images/generations'], limiter, async (req
|
|
| 215 |
try {
|
| 216 |
let targetUrl = isImage && selectedProvider.imageUrl ? selectedProvider.imageUrl : selectedProvider.url;
|
| 217 |
|
| 218 |
-
// --- OFUSCACIÓN
|
| 219 |
-
// Jamás enviamos IPs, Hostnames originales ni Referers.
|
| 220 |
const fetchHeaders = {
|
| 221 |
"Content-Type": "application/json",
|
| 222 |
"User-Agent": getRandomUserAgent(),
|
| 223 |
"Accept": "*/*",
|
| 224 |
-
"Connection": "keep-alive"
|
|
|
|
|
|
|
| 225 |
};
|
| 226 |
|
| 227 |
const response = await fetch(targetUrl, {
|
|
@@ -241,7 +240,7 @@ app.post(['/v1/chat/completions', '/v1/images/generations'], limiter, async (req
|
|
| 241 |
responseHeaders.delete('server');
|
| 242 |
responseHeaders.delete('x-powered-by');
|
| 243 |
responseHeaders.delete('cf-ray');
|
| 244 |
-
responseHeaders.delete('access-control-allow-origin');
|
| 245 |
|
| 246 |
if (isImage) {
|
| 247 |
const contentType = responseHeaders.get("content-type") || "";
|
|
@@ -264,8 +263,6 @@ app.post(['/v1/chat/completions', '/v1/images/generations'], limiter, async (req
|
|
| 264 |
}
|
| 265 |
releaseSlot();
|
| 266 |
responseSent = true;
|
| 267 |
-
|
| 268 |
-
// Purga manual de variables
|
| 269 |
payload = null;
|
| 270 |
|
| 271 |
return res.status(response.status).json({
|
|
@@ -299,7 +296,6 @@ app.post(['/v1/chat/completions', '/v1/images/generations'], limiter, async (req
|
|
| 299 |
}
|
| 300 |
}
|
| 301 |
|
| 302 |
-
// Streaming (Texto)
|
| 303 |
res.writeHead(response.status, {
|
| 304 |
'Content-Type': responseHeaders.get('content-type') || 'text/event-stream',
|
| 305 |
'Cache-Control': 'no-cache',
|
|
@@ -312,7 +308,7 @@ app.post(['/v1/chat/completions', '/v1/images/generations'], limiter, async (req
|
|
| 312 |
|
| 313 |
stream.on('end', () => {
|
| 314 |
releaseSlot();
|
| 315 |
-
payload = null;
|
| 316 |
});
|
| 317 |
stream.on('error', () => {
|
| 318 |
releaseSlot();
|
|
@@ -337,7 +333,6 @@ app.post(['/v1/chat/completions', '/v1/images/generations'], limiter, async (req
|
|
| 337 |
}
|
| 338 |
}
|
| 339 |
|
| 340 |
-
// Purga final de seguridad si falla todo
|
| 341 |
payload = null;
|
| 342 |
|
| 343 |
if (!responseSent) {
|
|
@@ -346,5 +341,5 @@ app.post(['/v1/chat/completions', '/v1/images/generations'], limiter, async (req
|
|
| 346 |
});
|
| 347 |
|
| 348 |
app.listen(PORT, '0.0.0.0', () => {
|
| 349 |
-
console.log(`[SYS] Nivel de cifrado: Tor. Puerto: ${PORT}`);
|
| 350 |
});
|
|
|
|
| 8 |
const PORT = 7860;
|
| 9 |
|
| 10 |
// --- 1. CONFIGURACIÓN DE SEGURIDAD PARANOICA ---
|
| 11 |
+
// CRÍTICO: trust proxy en 1 es vital para leer la IP real del usuario a través de Cloudflare/HF
|
| 12 |
app.set('trust proxy', 1);
|
| 13 |
app.disable('x-powered-by');
|
| 14 |
app.use(cors());
|
| 15 |
|
| 16 |
app.use((req, res, next) => {
|
|
|
|
| 17 |
res.setHeader('X-Content-Type-Options', 'nosniff');
|
| 18 |
res.setHeader('X-Frame-Options', 'DENY');
|
| 19 |
res.setHeader('X-XSS-Protection', '1; mode=block');
|
| 20 |
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
|
| 21 |
+
res.setHeader('Referrer-Policy', 'no-referrer'); // Oculta que vienen de Ventarys AI
|
| 22 |
res.setHeader('Content-Security-Policy', "default-src 'none'; frame-ancestors 'none';");
|
| 23 |
next();
|
| 24 |
});
|
|
|
|
| 27 |
app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
| 28 |
|
| 29 |
function logError(providerId, reason) {
|
|
|
|
|
|
|
| 30 |
console.error(`[SYSTEM] Provider Error: ${providerId} | ${reason}`);
|
| 31 |
}
|
| 32 |
|
| 33 |
+
// Falsificación de huella digital del navegador
|
| 34 |
function getRandomUserAgent() {
|
| 35 |
const agents = [
|
| 36 |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
|
|
| 58 |
}
|
| 59 |
];
|
| 60 |
|
| 61 |
+
// CRÍTICO: Subimos el límite a 500 para permitir alta concurrencia.
|
| 62 |
+
// Ahora el proxy dejará pasar todo el tráfico y el proveedor final aplicará el rate limit por IP.
|
| 63 |
+
const MAX_PER_PROVIDER = 500;
|
| 64 |
+
const QUEUE_TIMEOUT = 45000;
|
| 65 |
let currentLoad = { "pollinations-ai": 0, "kepler-cloud": 0, "voids-api": 0 };
|
| 66 |
|
| 67 |
// --- RATE LIMITING LOCAL ---
|
|
|
|
|
|
|
| 68 |
const limiter = rateLimit({
|
| 69 |
windowMs: 60 * 1000,
|
| 70 |
+
max: 50, // 50 peticiones por IP localmente para evitar que alguien tire TU servidor
|
| 71 |
keyGenerator: (req) => req.ip,
|
| 72 |
message: { error: { message: "Too many requests.", code: 429 } },
|
| 73 |
+
standardHeaders: false,
|
| 74 |
legacyHeaders: false,
|
| 75 |
});
|
| 76 |
|
|
|
|
| 97 |
method: "GET",
|
| 98 |
headers: {
|
| 99 |
"Content-Type": "application/json",
|
| 100 |
+
"User-Agent": getRandomUserAgent()
|
| 101 |
}
|
| 102 |
});
|
| 103 |
if (!resp.ok) return [];
|
|
|
|
| 134 |
}
|
| 135 |
|
| 136 |
app.get('/health', (req, res) => {
|
|
|
|
| 137 |
res.json({ status: "online" });
|
| 138 |
});
|
| 139 |
|
|
|
|
| 179 |
const startTime = Date.now();
|
| 180 |
let responseSent = false;
|
| 181 |
|
| 182 |
+
// Extraemos la IP del cliente para reenviarla
|
| 183 |
+
const clientIp = req.ip || req.headers['x-forwarded-for'] || req.socket.remoteAddress;
|
| 184 |
+
|
| 185 |
let payload = JSON.stringify(req.body);
|
| 186 |
+
req.body = null;
|
| 187 |
|
| 188 |
while (availableProviders.length > 0 && Date.now() - startTime < QUEUE_TIMEOUT) {
|
| 189 |
let selectedProvider = null;
|
|
|
|
| 213 |
try {
|
| 214 |
let targetUrl = isImage && selectedProvider.imageUrl ? selectedProvider.imageUrl : selectedProvider.url;
|
| 215 |
|
| 216 |
+
// --- OFUSCACIÓN + DELEGACIÓN DE IP ---
|
|
|
|
| 217 |
const fetchHeaders = {
|
| 218 |
"Content-Type": "application/json",
|
| 219 |
"User-Agent": getRandomUserAgent(),
|
| 220 |
"Accept": "*/*",
|
| 221 |
+
"Connection": "keep-alive",
|
| 222 |
+
"X-Forwarded-For": clientIp, // El proveedor rate-limitea esta IP
|
| 223 |
+
"X-Real-IP": clientIp // El proveedor rate-limitea esta IP
|
| 224 |
};
|
| 225 |
|
| 226 |
const response = await fetch(targetUrl, {
|
|
|
|
| 240 |
responseHeaders.delete('server');
|
| 241 |
responseHeaders.delete('x-powered-by');
|
| 242 |
responseHeaders.delete('cf-ray');
|
| 243 |
+
responseHeaders.delete('access-control-allow-origin');
|
| 244 |
|
| 245 |
if (isImage) {
|
| 246 |
const contentType = responseHeaders.get("content-type") || "";
|
|
|
|
| 263 |
}
|
| 264 |
releaseSlot();
|
| 265 |
responseSent = true;
|
|
|
|
|
|
|
| 266 |
payload = null;
|
| 267 |
|
| 268 |
return res.status(response.status).json({
|
|
|
|
| 296 |
}
|
| 297 |
}
|
| 298 |
|
|
|
|
| 299 |
res.writeHead(response.status, {
|
| 300 |
'Content-Type': responseHeaders.get('content-type') || 'text/event-stream',
|
| 301 |
'Cache-Control': 'no-cache',
|
|
|
|
| 308 |
|
| 309 |
stream.on('end', () => {
|
| 310 |
releaseSlot();
|
| 311 |
+
payload = null;
|
| 312 |
});
|
| 313 |
stream.on('error', () => {
|
| 314 |
releaseSlot();
|
|
|
|
| 333 |
}
|
| 334 |
}
|
| 335 |
|
|
|
|
| 336 |
payload = null;
|
| 337 |
|
| 338 |
if (!responseSent) {
|
|
|
|
| 341 |
});
|
| 342 |
|
| 343 |
app.listen(PORT, '0.0.0.0', () => {
|
| 344 |
+
console.log(`[SYS] Nivel de cifrado: Tor (Delegando IP). Concurrencia máxima: 500/prov. Puerto: ${PORT}`);
|
| 345 |
});
|