fix(sftp): resolve trusted local tar before cwd switch

Use the Windows System32 tar.exe for local availability checks and compression so an upload sibling tar.exe cannot hijack execution. Keep Unix tar lookup unchanged and update command assertions.

Residual P2: exclude patterns still interpret glob metacharacters in archive paths; glob escaping is intentionally deferred.
This commit is contained in:
陈大猫 committed 2026-10-29 19:46:53 +08:00
1 parent 4abe993250
commit 73ff25a314
2 files changed
+17 -3

No files matched your search

+11 -2
View File
@@ -395,12 +395,21 @@ function init(deps) {
transferBridge = deps.transferBridge;
}
/**
* Resolve Windows tar before switching to an upload-controlled directory.
*/
function resolveLocalTarCommand() {
return process.platform === 'win32'
? path.join(process.env.SystemRoot || process.env.windir || 'C:\\Windows', 'System32', 'tar.exe')
: 'tar';
}
/**
* Check if tar command is available on the system
*/
async function checkTarAvailable(signal) {
return new Promise((resolve) => {
const tar = spawn('tar', ['--version'], { stdio: 'ignore' });
const tar = spawn(resolveLocalTarCommand(), ['--version'], { stdio: 'ignore' });
let settled = false;
let childClosed = false;
let forceKillTimer = null;
@@ -513,7 +522,7 @@ async function compressFolder(folderPath, outputPath, compressionId, sendProgres
tarArgs.push(`--exclude=${path.relative(parentDir, outputPath).split(path.sep).join('/')}`);
}
tarArgs.push(folderName);
const tar = spawn('tar', tarArgs, {
const tar = spawn(resolveLocalTarCommand(), tarArgs, {
cwd: parentDir,
stdio: ['ignore', outputFd, 'pipe']
});
@@ -9,6 +9,10 @@ const path = require("node:path");
const { EventEmitter } = require("node:events");
const test = require("node:test");
const localTarCommand = process.platform === "win32"
? path.join(process.env.SystemRoot || process.env.windir || "C:\\Windows", "System32", "tar.exe")
: "tar";
// The bridge destructures spawn and getTempFilePath at load time, so record
// tar calls and redirect temp archives before requiring it.
let tempDir = null;
@@ -91,7 +95,7 @@ async function compressAndList(folderPath) {
folderName: "folder",
totalBytes: 7,
});
const tarCall = spawnCalls.find((call) => call.command === "tar" && call.args.includes("-czf"));
const tarCall = spawnCalls.find((call) => call.command === localTarCommand && call.args.includes("-czf"));
return { result, archiveEntries, tarCall };
}
@@ -103,6 +107,7 @@ test("local tar gets its paths through cwd and stdout instead of argv", async (t
const { result, archiveEntries, tarCall } = await compressAndList(folderPath);
assert.equal(result.success, true);
assert.equal(spawnCalls.find((call) => call.args.includes("--version"))?.command, localTarCommand);
assert.ok(tarCall, "local tar compression must be spawned");
assert.equal(tarCall.options.cwd, path.dirname(folderPath));
assert.equal(tarCall.args.includes("-C"), false, "parent directory must not be passed through argv");