security: block apply_patch path traversal outside workspace (#16405)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 0fcd3f8c3a
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
This commit is contained in:
Mariano
2026-02-14 19:11:12 +00:00
committed by GitHub
parent 4734f99108
commit 5544646a09
3 changed files with 77 additions and 28 deletions

View File

@@ -1,10 +1,10 @@
import type { AgentTool } from "@mariozechner/pi-agent-core";
import { Type } from "@sinclair/typebox";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import type { SandboxFsBridge } from "./sandbox/fs-bridge.js";
import { applyUpdateHunk } from "./apply-patch-update.js";
import { assertSandboxPath } from "./sandbox-paths.js";
const BEGIN_PATCH_MARKER = "*** Begin Patch";
const END_PATCH_MARKER = "*** End Patch";
@@ -15,7 +15,6 @@ const MOVE_TO_MARKER = "*** Move to: ";
const EOF_MARKER = "*** End of File";
const CHANGE_CONTEXT_MARKER = "@@ ";
const EMPTY_CHANGE_CONTEXT_MARKER = "@@";
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
type AddFileHunk = {
kind: "add";
@@ -262,36 +261,17 @@ async function resolvePatchPath(
};
}
const resolved = resolvePathFromCwd(filePath, options.cwd);
const resolved = await assertSandboxPath({
filePath,
cwd: options.cwd,
root: options.cwd,
});
return {
resolved,
display: toDisplayPath(resolved, options.cwd),
resolved: resolved.resolved,
display: toDisplayPath(resolved.resolved, options.cwd),
};
}
function normalizeUnicodeSpaces(value: string): string {
return value.replace(UNICODE_SPACES, " ");
}
function expandPath(filePath: string): string {
const normalized = normalizeUnicodeSpaces(filePath);
if (normalized === "~") {
return os.homedir();
}
if (normalized.startsWith("~/")) {
return os.homedir() + normalized.slice(1);
}
return normalized;
}
function resolvePathFromCwd(filePath: string, cwd: string): string {
const expanded = expandPath(filePath);
if (path.isAbsolute(expanded)) {
return path.normalize(expanded);
}
return path.resolve(cwd, expanded);
}
function toDisplayPath(resolved: string, cwd: string): string {
const relative = path.relative(cwd, resolved);
if (!relative || relative === "") {