From 88e73bd69c554382efc4dc7ca71db78e7aeaf34b Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Wed, 13 May 2026 19:10:12 +0800 Subject: [PATCH] fix: add sudo fallback for file read/write in editor --- src/backend/ssh/file-manager.ts | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/backend/ssh/file-manager.ts b/src/backend/ssh/file-manager.ts index 4bed2271..4ddfa48e 100644 --- a/src/backend/ssh/file-manager.ts +++ b/src/backend/ssh/file-manager.ts @@ -3058,6 +3058,50 @@ app.get("/ssh/file_manager/ssh/readFile", (req, res) => { stream.on("close", (code) => { if (code !== 0) { + const isPermissionDenied = + errorData.toLowerCase().includes("permission denied"); + + if (isPermissionDenied && sshConn.sudoPassword) { + execWithSudo( + sshConn, + `cat '${escapedPath}'`, + sshConn.sudoPassword, + (sudoErr, sudoStream) => { + if (sudoErr) { + return res.status(403).json({ + error: "Permission denied", + needsSudo: true, + }); + } + let sudoData = Buffer.alloc(0); + let sudoErrorData = ""; + sudoStream.on("data", (chunk: Buffer) => { + sudoData = Buffer.concat([sudoData, chunk]); + }); + sudoStream.stderr.on("data", (chunk: Buffer) => { + sudoErrorData += chunk.toString(); + }); + sudoStream.on("close", (sudoCode) => { + if (sudoCode !== 0) { + return res.status(403).json({ + error: `Permission denied: ${sudoErrorData}`, + needsSudo: true, + }); + } + const isBinary = detectBinary(sudoData); + res.json({ + content: isBinary + ? sudoData.toString("base64") + : sudoData.toString("utf8"), + isBinary, + size: sudoData.length, + }); + }); + }, + ); + return; + } + fileLogger.error( `SSH readFile command failed with code ${code}: ${errorData.replace(/\n/g, " ").trim()}`, ); @@ -3490,12 +3534,39 @@ app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => { } }); } else { + const isPermDenied = errorData.toLowerCase().includes("permission denied"); + if (isPermDenied && sshConn.sudoPassword) { + const sudoWriteCmd = `echo '${base64Content}' | base64 -d | sudo tee '${escapedPath}' > /dev/null && echo "SUCCESS"`; + execWithSudo(sshConn, `bash -c "echo '${base64Content}' | base64 -d > '${escapedPath}' && echo SUCCESS"`, sshConn.sudoPassword, (sudoErr, sudoStream) => { + if (sudoErr) { + if (!res.headersSent) { + res.status(403).json({ error: "Permission denied", needsSudo: true }); + } + return; + } + let sudoOut = ""; + sudoStream.on("data", (chunk: Buffer) => { sudoOut += chunk.toString(); }); + sudoStream.on("close", () => { + if (sudoOut.includes("SUCCESS")) { + restoreOriginalMode(null, () => { + if (!res.headersSent) { + res.json({ message: "File written successfully", path: filePath }); + } + }); + } else if (!res.headersSent) { + res.status(403).json({ error: "Permission denied", needsSudo: true }); + } + }); + }); + return; + } fileLogger.error( `Fallback write failed with code ${code}: ${errorData}`, ); if (!res.headersSent) { res.status(500).json({ error: `Write failed: ${errorData}`, + needsSudo: isPermDenied, toast: { type: "error", message: `Write failed: ${errorData}` }, }); }