mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-04 23:44:41 +00:00
fix(server): disable repository fsmonitor commands (#4208)
A repository-controlled core.fsmonitor command can execute when the daemon refreshes Git state. Opening or reconciling a malicious checkout therefore allows code execution as the daemon user. Force core.fsmonitor=false on daemon-owned Git invocations before repository config is considered.
This commit is contained in:
@@ -227,7 +227,7 @@ async function collectProviderEntries(
|
||||
|
||||
async function collectToolEntries(): Promise<DiagnosticEntry[]> {
|
||||
const [git, gh] = await Promise.all([
|
||||
checkTool("git", ["--version"]),
|
||||
checkTool("git", ["-c", "core.fsmonitor=false", "--version"]),
|
||||
checkTool("gh", ["--version"]),
|
||||
]);
|
||||
return [
|
||||
|
||||
@@ -16,10 +16,10 @@ vi.mock("child_process", async () => {
|
||||
const [command, commandArgs] = args;
|
||||
if (command === "git" && Array.isArray(commandArgs)) {
|
||||
const normalizedArgs = commandArgs.map((arg) => String(arg));
|
||||
// `runGitCommand` always prepends `-c core.quotepath=false`; skip it to
|
||||
// find the actual git subcommand.
|
||||
// `runGitCommand` always prepends its two config overrides; skip them
|
||||
// to find the actual git subcommand.
|
||||
const subcommandIndex =
|
||||
normalizedArgs[0] === "-c" && normalizedArgs[1] === "core.quotepath=false" ? 2 : 0;
|
||||
normalizedArgs[0] === "-c" && normalizedArgs[1] === "core.quotepath=false" ? 4 : 0;
|
||||
const isTrackedTextDiff =
|
||||
normalizedArgs[subcommandIndex] === "diff" &&
|
||||
normalizedArgs.includes("HEAD") &&
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { chmodSync, existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { runGitCommand } from "./run-git-command.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
function makeTempRepo(): string {
|
||||
const repo = mkdtempSync(path.join(tmpdir(), "paseo-git-fsmonitor-"));
|
||||
tempDirs.push(repo);
|
||||
return repo;
|
||||
}
|
||||
|
||||
async function configureFsmonitor(repo: string): Promise<string> {
|
||||
const hookPath = path.join(repo, "fsmonitor-hook.sh");
|
||||
const markerPath = `${hookPath}.marker`;
|
||||
writeFileSync(hookPath, "#!/bin/sh\n: > \"$0.marker\"\nprintf '1\\n'\n");
|
||||
chmodSync(hookPath, 0o755);
|
||||
await runGitCommand(["config", "core.fsmonitor", hookPath], { cwd: repo });
|
||||
return markerPath;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const dir of tempDirs.splice(0)) {
|
||||
rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
||||
}
|
||||
});
|
||||
|
||||
describe("runGitCommand fsmonitor isolation", () => {
|
||||
it("does not execute a repository-configured fsmonitor command", async () => {
|
||||
const repo = makeTempRepo();
|
||||
await runGitCommand(["init"], { cwd: repo });
|
||||
const markerPath = await configureFsmonitor(repo);
|
||||
|
||||
await runGitCommand(["status", "--porcelain"], { cwd: repo });
|
||||
|
||||
expect(existsSync(markerPath)).toBe(false);
|
||||
});
|
||||
|
||||
it("overrides core.fsmonitor for commands other than status", async () => {
|
||||
const repo = makeTempRepo();
|
||||
await runGitCommand(["init"], { cwd: repo });
|
||||
await configureFsmonitor(repo);
|
||||
|
||||
const result = await runGitCommand(["config", "--get", "core.fsmonitor"], { cwd: repo });
|
||||
|
||||
expect(result.stdout.trim()).toBe("false");
|
||||
});
|
||||
});
|
||||
@@ -377,12 +377,17 @@ function runGitCommandWithProvenance(
|
||||
try {
|
||||
// `core.quotepath=false` makes git emit raw UTF-8 paths instead of
|
||||
// octal-escaping non-ASCII bytes (e.g. `测试文件.txt` vs `"\346\265\213..."`).
|
||||
child = spawnProcess("git", ["-c", "core.quotepath=false", ...args], {
|
||||
cwd: options.cwd,
|
||||
envOverlay,
|
||||
shell: false,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
// `core.fsmonitor=false` prevents repository config from launching a command.
|
||||
child = spawnProcess(
|
||||
"git",
|
||||
["-c", "core.quotepath=false", "-c", "core.fsmonitor=false", ...args],
|
||||
{
|
||||
cwd: options.cwd,
|
||||
envOverlay,
|
||||
shell: false,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
},
|
||||
);
|
||||
spawnGitCommandTrace(commandTrace, child.pid);
|
||||
} catch (error) {
|
||||
rejectSpawnFailure(error);
|
||||
|
||||
Reference in New Issue
Block a user