Spaces:
Sleeping
Sleeping
Update server.js
Browse files
server.js
CHANGED
|
@@ -25,7 +25,6 @@ const ALLOWED_ORIGINS = [
|
|
| 25 |
// ================= SECURE IFRAME LOCK =================
|
| 26 |
app.use((req, res, next) => {
|
| 27 |
res.removeHeader("X-Frame-Options");
|
| 28 |
-
// Hugging Face compatibility ke liye ancestors ko allow kar rahe hain
|
| 29 |
res.setHeader(
|
| 30 |
"Content-Security-Policy",
|
| 31 |
"frame-ancestors 'self' https://*.hf.space http://localhost:* http://127.0.0.1:*"
|
|
@@ -38,7 +37,7 @@ let viteProcess = null;
|
|
| 38 |
let isBooting = false;
|
| 39 |
let buildRetryCount = 0;
|
| 40 |
const MAX_RETRIES = 3;
|
| 41 |
-
const bgProcesses = new Map();
|
| 42 |
|
| 43 |
// ================= PERSISTENT LOCK FILES =================
|
| 44 |
const FOUNDATION_LOCK = path.join(process.cwd(), ".foundation_lock");
|
|
@@ -46,9 +45,9 @@ const DEPS_LOCK = path.join(process.cwd(), ".deps_lock");
|
|
| 46 |
|
| 47 |
/* ================= BINARY FILE EXTENSIONS ================= */
|
| 48 |
const BINARY_EXTS = [
|
| 49 |
-
".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico", ".svg",
|
| 50 |
-
".pdf", ".zip", ".tar", ".gz",
|
| 51 |
-
".ttf", ".woff", ".woff2", ".otf", ".eot",
|
| 52 |
".mp3", ".mp4", ".wav"
|
| 53 |
];
|
| 54 |
|
|
@@ -73,7 +72,7 @@ setInterval(async () => {
|
|
| 73 |
if (idleMs > IDLE_TIMEOUT && viteProcess) {
|
| 74 |
pushLog(`[IDLE] No activity for ${Math.round(idleMs / 60000)} min β stopping Vite to save resources`, "warning");
|
| 75 |
await killVite();
|
| 76 |
-
bgProcesses.forEach((
|
| 77 |
}
|
| 78 |
}, 60 * 1000);
|
| 79 |
|
|
@@ -155,12 +154,23 @@ app.get("/api/logs", (req, res) => {
|
|
| 155 |
res.setHeader("Content-Type", "text/event-stream");
|
| 156 |
res.setHeader("Cache-Control", "no-cache");
|
| 157 |
res.setHeader("Connection", "keep-alive");
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
const last = parseInt(req.query.last) || 50;
|
| 159 |
-
logs.slice(-last).forEach(l => {
|
| 160 |
-
|
| 161 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
clients.add(res);
|
| 163 |
-
req.on("close", () =>
|
|
|
|
|
|
|
|
|
|
| 164 |
});
|
| 165 |
|
| 166 |
/* ================= MULTI-SERVER MANAGER ================= */
|
|
@@ -177,7 +187,6 @@ function startBgProcess(id, command, port = null) {
|
|
| 177 |
});
|
| 178 |
proc.stdout.on("data", d => pushLog(`[${id.toUpperCase()}] ${d.toString().trim()}`, "info"));
|
| 179 |
proc.stderr.on("data", d => pushLog(`[${id.toUpperCase()} ERROR] ${d.toString().trim()}`, "error"));
|
| 180 |
-
|
| 181 |
proc.on("close", code => {
|
| 182 |
pushLog(`[SERVER] Process ${id} stopped (code: ${code})`, "system");
|
| 183 |
bgProcesses.delete(id);
|
|
@@ -199,7 +208,6 @@ app.post("/api/process/start", (req, res) => {
|
|
| 199 |
if (!verifyToken(req, res)) return;
|
| 200 |
const { id, command, port } = req.body;
|
| 201 |
if (!id || !command) return res.status(400).json({ error: "ID and command required" });
|
| 202 |
-
|
| 203 |
const success = startBgProcess(id, command, port);
|
| 204 |
res.json({ success, message: success ? "Process started" : "Process already running" });
|
| 205 |
});
|
|
@@ -319,17 +327,24 @@ app.post("/api/update", async (req, res) => {
|
|
| 319 |
for (const [p, contentObj] of Object.entries(files)) {
|
| 320 |
const safePath = path.normalize(p).replace(/^(\.\.[\/\\])+/, "");
|
| 321 |
const fp = path.join(PROJECT_DIR, safePath);
|
| 322 |
-
|
| 323 |
-
|
|
|
|
|
|
|
| 324 |
await fs.ensureDir(path.dirname(fp));
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 328 |
await fs.writeFile(fp, Buffer.from(fileContent, "base64"));
|
| 329 |
} else {
|
| 330 |
await fs.writeFile(fp, fileContent, "utf-8");
|
| 331 |
}
|
| 332 |
-
|
| 333 |
updatedFiles.push(safePath);
|
| 334 |
}
|
| 335 |
if (!foundationAlreadyWritten) {
|
|
@@ -354,13 +369,12 @@ app.post("/api/read", async (req, res) => {
|
|
| 354 |
try {
|
| 355 |
const safePath = path.normalize(filePath).replace(/^(\.\.[\/\\])+/, "");
|
| 356 |
const fp = path.join(PROJECT_DIR, safePath);
|
| 357 |
-
|
| 358 |
if (!fp.startsWith(PROJECT_DIR)) return res.status(403).json({ error: "Path traversal blocked" });
|
| 359 |
if (!fs.existsSync(fp)) return res.status(404).json({ error: "File not found" });
|
| 360 |
const isBinary = isBinaryFile(safePath);
|
| 361 |
-
const content = isBinary
|
| 362 |
-
|
| 363 |
-
|
| 364 |
res.json({ success: true, content, path: safePath, isBase64: isBinary });
|
| 365 |
} catch (e) {
|
| 366 |
res.status(500).json({ error: e.message });
|
|
@@ -416,7 +430,6 @@ app.post("/api/execute", (req, res) => {
|
|
| 416 |
updateActivity(req);
|
| 417 |
const { command, timeout = 30000 } = req.body;
|
| 418 |
const isBlocked = BLOCKED_COMMANDS.some(cmd => command.includes(cmd));
|
| 419 |
-
|
| 420 |
if (isBlocked) {
|
| 421 |
pushLog(`[SECURITY] Blocked dangerous command: ${command}`, "error");
|
| 422 |
return res.status(403).json({ error: "COMMAND_BLOCKED" });
|
|
@@ -462,9 +475,9 @@ app.post("/api/vite/stop", async (req, res) => {
|
|
| 462 |
app.post("/api/reset", async (req, res) => {
|
| 463 |
if (!verifyToken(req, res)) return;
|
| 464 |
try {
|
| 465 |
-
pushLog("[SYSTEM]
|
| 466 |
await killVite();
|
| 467 |
-
bgProcesses.forEach((
|
| 468 |
await fs.remove(PROJECT_DIR);
|
| 469 |
fs.mkdirSync(PROJECT_DIR, { recursive: true });
|
| 470 |
if (fs.existsSync(FOUNDATION_LOCK)) fs.removeSync(FOUNDATION_LOCK);
|
|
@@ -488,13 +501,10 @@ const PREVIEW_SECRET = "AUTODEV_PREVIEW_777";
|
|
| 488 |
const ACCESS_DENIED_PAGE = `<!DOCTYPE html><html><head><title>Access Denied</title><style>body{background:#09090b;color:#fff;font-family:monospace;display:flex;align-items:center;justify-content:center;height:100vh;flex-direction:column;gap:16px;}.title{color:#ef4444;font-size:1.4rem;font-weight:bold;}</style></head><body><div class="title">ACCESS DENIED</div><div>This preview is protected</div></body></html>`;
|
| 489 |
const BOOTING_PAGE = `<!DOCTYPE html><html><head><title>Booting...</title><style>body{background:#09090b;color:#facc15;font-family:monospace;display:flex;align-items:center;justify-content:center;height:100vh;}</style><script>setTimeout(()=>location.reload(), 2000)</script></head><body>β‘ AutoDev is booting up...</body></html>`;
|
| 490 |
|
| 491 |
-
//
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
res.setHeader("Content-Security-Policy", "frame-ancestors *");
|
| 496 |
-
res.setHeader("X-Frame-Options", "ALLOWALL");
|
| 497 |
-
});
|
| 498 |
|
| 499 |
proxy.on("error", (err, req, res) => {
|
| 500 |
if (res && typeof res.writeHead === "function") {
|
|
@@ -514,6 +524,7 @@ app.use((req, res, next) => {
|
|
| 514 |
const previewHeader = req.headers["x-autodev-preview"];
|
| 515 |
const previewParam = req.query["__autodev"];
|
| 516 |
if (previewHeader !== PREVIEW_SECRET && previewParam !== PREVIEW_SECRET) {
|
|
|
|
| 517 |
return res.status(403).send(ACCESS_DENIED_PAGE);
|
| 518 |
}
|
| 519 |
}
|
|
@@ -532,13 +543,13 @@ server.on("upgrade", (req, socket, head) => proxy.ws(req, socket, head));
|
|
| 532 |
process.on("SIGTERM", async () => {
|
| 533 |
pushLog("[SYSTEM] Shutting down...", "system");
|
| 534 |
await killVite();
|
| 535 |
-
bgProcesses.forEach((
|
| 536 |
process.exit(0);
|
| 537 |
});
|
| 538 |
|
| 539 |
process.on("SIGINT", async () => {
|
| 540 |
await killVite();
|
| 541 |
-
bgProcesses.forEach((
|
| 542 |
process.exit(0);
|
| 543 |
});
|
| 544 |
|
|
|
|
| 25 |
// ================= SECURE IFRAME LOCK =================
|
| 26 |
app.use((req, res, next) => {
|
| 27 |
res.removeHeader("X-Frame-Options");
|
|
|
|
| 28 |
res.setHeader(
|
| 29 |
"Content-Security-Policy",
|
| 30 |
"frame-ancestors 'self' https://*.hf.space http://localhost:* http://127.0.0.1:*"
|
|
|
|
| 37 |
let isBooting = false;
|
| 38 |
let buildRetryCount = 0;
|
| 39 |
const MAX_RETRIES = 3;
|
| 40 |
+
const bgProcesses = new Map();
|
| 41 |
|
| 42 |
// ================= PERSISTENT LOCK FILES =================
|
| 43 |
const FOUNDATION_LOCK = path.join(process.cwd(), ".foundation_lock");
|
|
|
|
| 45 |
|
| 46 |
/* ================= BINARY FILE EXTENSIONS ================= */
|
| 47 |
const BINARY_EXTS = [
|
| 48 |
+
".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico", ".svg",
|
| 49 |
+
".pdf", ".zip", ".tar", ".gz",
|
| 50 |
+
".ttf", ".woff", ".woff2", ".otf", ".eot",
|
| 51 |
".mp3", ".mp4", ".wav"
|
| 52 |
];
|
| 53 |
|
|
|
|
| 72 |
if (idleMs > IDLE_TIMEOUT && viteProcess) {
|
| 73 |
pushLog(`[IDLE] No activity for ${Math.round(idleMs / 60000)} min β stopping Vite to save resources`, "warning");
|
| 74 |
await killVite();
|
| 75 |
+
bgProcesses.forEach((_, id) => killBgProcess(id));
|
| 76 |
}
|
| 77 |
}, 60 * 1000);
|
| 78 |
|
|
|
|
| 154 |
res.setHeader("Content-Type", "text/event-stream");
|
| 155 |
res.setHeader("Cache-Control", "no-cache");
|
| 156 |
res.setHeader("Connection", "keep-alive");
|
| 157 |
+
// BUG FIX #1: X-Accel-Buffering missing tha β HF/nginx ke peeche SSE buffer
|
| 158 |
+
// ho jaata tha, logs real-time nahi aate the client tak
|
| 159 |
+
res.setHeader("X-Accel-Buffering", "no");
|
| 160 |
+
|
| 161 |
const last = parseInt(req.query.last) || 50;
|
| 162 |
+
logs.slice(-last).forEach(l => res.write(`data: ${JSON.stringify(l)}\n\n`));
|
| 163 |
+
|
| 164 |
+
// Heartbeat β 30s mein connection alive rakhne ke liye
|
| 165 |
+
const heartbeat = setInterval(() => {
|
| 166 |
+
try { res.write(`: heartbeat\n\n`); } catch { clearInterval(heartbeat); }
|
| 167 |
+
}, 30000);
|
| 168 |
+
|
| 169 |
clients.add(res);
|
| 170 |
+
req.on("close", () => {
|
| 171 |
+
clients.delete(res);
|
| 172 |
+
clearInterval(heartbeat);
|
| 173 |
+
});
|
| 174 |
});
|
| 175 |
|
| 176 |
/* ================= MULTI-SERVER MANAGER ================= */
|
|
|
|
| 187 |
});
|
| 188 |
proc.stdout.on("data", d => pushLog(`[${id.toUpperCase()}] ${d.toString().trim()}`, "info"));
|
| 189 |
proc.stderr.on("data", d => pushLog(`[${id.toUpperCase()} ERROR] ${d.toString().trim()}`, "error"));
|
|
|
|
| 190 |
proc.on("close", code => {
|
| 191 |
pushLog(`[SERVER] Process ${id} stopped (code: ${code})`, "system");
|
| 192 |
bgProcesses.delete(id);
|
|
|
|
| 208 |
if (!verifyToken(req, res)) return;
|
| 209 |
const { id, command, port } = req.body;
|
| 210 |
if (!id || !command) return res.status(400).json({ error: "ID and command required" });
|
|
|
|
| 211 |
const success = startBgProcess(id, command, port);
|
| 212 |
res.json({ success, message: success ? "Process started" : "Process already running" });
|
| 213 |
});
|
|
|
|
| 327 |
for (const [p, contentObj] of Object.entries(files)) {
|
| 328 |
const safePath = path.normalize(p).replace(/^(\.\.[\/\\])+/, "");
|
| 329 |
const fp = path.join(PROJECT_DIR, safePath);
|
| 330 |
+
if (!fp.startsWith(PROJECT_DIR)) {
|
| 331 |
+
pushLog(`[SECURITY] Blocked path traversal: ${p}`, "error");
|
| 332 |
+
continue;
|
| 333 |
+
}
|
| 334 |
await fs.ensureDir(path.dirname(fp));
|
| 335 |
+
// BUG FIX #2: contentObj null/undefined hone par crash hota tha
|
| 336 |
+
// typeof check se pehle null guard lagao
|
| 337 |
+
const fileContent = contentObj == null
|
| 338 |
+
? ""
|
| 339 |
+
: typeof contentObj === "string"
|
| 340 |
+
? contentObj
|
| 341 |
+
: contentObj.content ?? "";
|
| 342 |
+
const isBase64 = contentObj?.isBase64 || isBinaryFile(safePath);
|
| 343 |
+
if (isBase64 && fileContent) {
|
| 344 |
await fs.writeFile(fp, Buffer.from(fileContent, "base64"));
|
| 345 |
} else {
|
| 346 |
await fs.writeFile(fp, fileContent, "utf-8");
|
| 347 |
}
|
|
|
|
| 348 |
updatedFiles.push(safePath);
|
| 349 |
}
|
| 350 |
if (!foundationAlreadyWritten) {
|
|
|
|
| 369 |
try {
|
| 370 |
const safePath = path.normalize(filePath).replace(/^(\.\.[\/\\])+/, "");
|
| 371 |
const fp = path.join(PROJECT_DIR, safePath);
|
|
|
|
| 372 |
if (!fp.startsWith(PROJECT_DIR)) return res.status(403).json({ error: "Path traversal blocked" });
|
| 373 |
if (!fs.existsSync(fp)) return res.status(404).json({ error: "File not found" });
|
| 374 |
const isBinary = isBinaryFile(safePath);
|
| 375 |
+
const content = isBinary
|
| 376 |
+
? await fs.readFile(fp, "base64")
|
| 377 |
+
: await fs.readFile(fp, "utf-8");
|
| 378 |
res.json({ success: true, content, path: safePath, isBase64: isBinary });
|
| 379 |
} catch (e) {
|
| 380 |
res.status(500).json({ error: e.message });
|
|
|
|
| 430 |
updateActivity(req);
|
| 431 |
const { command, timeout = 30000 } = req.body;
|
| 432 |
const isBlocked = BLOCKED_COMMANDS.some(cmd => command.includes(cmd));
|
|
|
|
| 433 |
if (isBlocked) {
|
| 434 |
pushLog(`[SECURITY] Blocked dangerous command: ${command}`, "error");
|
| 435 |
return res.status(403).json({ error: "COMMAND_BLOCKED" });
|
|
|
|
| 475 |
app.post("/api/reset", async (req, res) => {
|
| 476 |
if (!verifyToken(req, res)) return;
|
| 477 |
try {
|
| 478 |
+
pushLog("[SYSTEM] Resetting project...", "warning");
|
| 479 |
await killVite();
|
| 480 |
+
bgProcesses.forEach((_, id) => killBgProcess(id));
|
| 481 |
await fs.remove(PROJECT_DIR);
|
| 482 |
fs.mkdirSync(PROJECT_DIR, { recursive: true });
|
| 483 |
if (fs.existsSync(FOUNDATION_LOCK)) fs.removeSync(FOUNDATION_LOCK);
|
|
|
|
| 501 |
const ACCESS_DENIED_PAGE = `<!DOCTYPE html><html><head><title>Access Denied</title><style>body{background:#09090b;color:#fff;font-family:monospace;display:flex;align-items:center;justify-content:center;height:100vh;flex-direction:column;gap:16px;}.title{color:#ef4444;font-size:1.4rem;font-weight:bold;}</style></head><body><div class="title">ACCESS DENIED</div><div>This preview is protected</div></body></html>`;
|
| 502 |
const BOOTING_PAGE = `<!DOCTYPE html><html><head><title>Booting...</title><style>body{background:#09090b;color:#facc15;font-family:monospace;display:flex;align-items:center;justify-content:center;height:100vh;}</style><script>setTimeout(()=>location.reload(), 2000)</script></head><body>β‘ AutoDev is booting up...</body></html>`;
|
| 503 |
|
| 504 |
+
// BUG FIX #3: proxyRes handler WRONG tha β http-proxy already headers forward
|
| 505 |
+
// kar chuka hota hai jab proxyRes fire hota hai, toh wahan set karna useless tha.
|
| 506 |
+
// Sahi fix: upar wala app.use iframe middleware engine responses cover karta hai.
|
| 507 |
+
// proxyRes handler hata diya.
|
|
|
|
|
|
|
|
|
|
| 508 |
|
| 509 |
proxy.on("error", (err, req, res) => {
|
| 510 |
if (res && typeof res.writeHead === "function") {
|
|
|
|
| 524 |
const previewHeader = req.headers["x-autodev-preview"];
|
| 525 |
const previewParam = req.query["__autodev"];
|
| 526 |
if (previewHeader !== PREVIEW_SECRET && previewParam !== PREVIEW_SECRET) {
|
| 527 |
+
pushLog(`[SECURITY] Preview blocked β direct access attempt`, "warning");
|
| 528 |
return res.status(403).send(ACCESS_DENIED_PAGE);
|
| 529 |
}
|
| 530 |
}
|
|
|
|
| 543 |
process.on("SIGTERM", async () => {
|
| 544 |
pushLog("[SYSTEM] Shutting down...", "system");
|
| 545 |
await killVite();
|
| 546 |
+
bgProcesses.forEach((_, id) => killBgProcess(id));
|
| 547 |
process.exit(0);
|
| 548 |
});
|
| 549 |
|
| 550 |
process.on("SIGINT", async () => {
|
| 551 |
await killVite();
|
| 552 |
+
bgProcesses.forEach((_, id) => killBgProcess(id));
|
| 553 |
process.exit(0);
|
| 554 |
});
|
| 555 |
|