fix: harden apply-patch v2 remote reads

This commit is contained in:
Codex
2026-05-28 10:33:46 +00:00
parent dc227f3e7d
commit 03b6b891c4
+55 -3
View File
@@ -281,6 +281,7 @@ async function executePlannedOperation(executor: ApplyPatchV2Executor, operation
const readBlockBytes = 45_000;
const writeB64ArgvLimit = 48_000;
const writeB64ChunkChars = 12_000;
const remoteReadBlockMarker = "UNIDESK_APPLY_PATCH_V2_BLOCK";
async function readRemoteText(executor: ApplyPatchV2Executor, target: string): Promise<string> {
if (executor.fs) {
@@ -333,8 +334,8 @@ async function readRemoteText(executor: ApplyPatchV2Executor, target: string): P
let actualBytes = 0;
for (let blockIndex = 0; actualBytes < expectedBytes; blockIndex += 1) {
const read = await checkedRemoteV2(executor, "read-b64-block", [target, String(blockIndex), String(readBlockBytes)]);
const encoded = read.stdout.replace(/\s+/gu, "");
const chunk = encoded.length === 0 ? Buffer.alloc(0) : Buffer.from(encoded, "base64");
const expectedChunkBytes = Math.min(readBlockBytes, expectedBytes - actualBytes);
const chunk = decodeRemoteReadBlock(read.stdout, target, blockIndex, expectedChunkBytes);
if (chunk.length === 0) {
throw new ApplyPatchV2Error("remote apply-patch v2 read returned an empty block before EOF", {
path: target,
@@ -366,6 +367,50 @@ async function readRemoteText(executor: ApplyPatchV2Executor, target: string): P
return contentBuffer.toString("utf8");
}
function decodeRemoteReadBlock(stdout: string, target: string, blockIndex: number, expectedChunkBytes: number): Buffer {
const lines = stdout.split(/\r?\n/u);
const markerIndex = lines.findIndex((line) => line.startsWith(`${remoteReadBlockMarker} `));
let encoded = "";
let declaredBytes: number | null = null;
let declaredSha256: string | null = null;
if (markerIndex >= 0) {
const [, bytesText, sha256] = lines[markerIndex].split(/\s+/u);
declaredBytes = Number(bytesText);
declaredSha256 = sha256 ?? null;
if (!Number.isSafeInteger(declaredBytes) || declaredBytes < 0 || !/^[0-9a-f]{64}$/u.test(declaredSha256 ?? "")) {
throw new ApplyPatchV2Error("remote apply-patch v2 read block returned invalid metadata", {
path: target,
blockIndex,
marker: lines[markerIndex].slice(0, 200),
});
}
encoded = (lines[markerIndex + 1] ?? "").replace(/\s+/gu, "");
} else {
encoded = stdout.replace(/\s+/gu, "");
}
const decoded = encoded.length === 0 ? Buffer.alloc(0) : Buffer.from(encoded, "base64");
if (declaredBytes !== null) {
if (decoded.length !== declaredBytes) {
throw new ApplyPatchV2Error("remote apply-patch v2 read block byte count mismatch", {
path: target,
blockIndex,
expectedBytes: declaredBytes,
actualBytes: decoded.length,
});
}
const actualSha256 = sha256Hex(decoded);
if (actualSha256 !== declaredSha256) {
throw new ApplyPatchV2Error("remote apply-patch v2 read block sha256 mismatch", {
path: target,
blockIndex,
expectedSha256: declaredSha256,
actualSha256,
});
}
}
return decoded.length > expectedChunkBytes ? decoded.subarray(0, expectedChunkBytes) : decoded;
}
async function writeRemoteText(executor: ApplyPatchV2Executor, target: string, content: string): Promise<void> {
const contentBuffer = Buffer.from(content, "utf8");
if (executor.fs) {
@@ -473,7 +518,14 @@ function remoteV2Script(operation: RemoteV2Operation, args: string[]): string[]
" block_index=$2",
" block_size=$3",
" case \"$block_index:$block_size\" in *[!0-9:]*|:*) printf 'invalid read block args\\n' >&2; exit 2;; esac",
" dd if=\"$target\" bs=\"$block_size\" skip=\"$block_index\" count=1 2>/dev/null | base64 | tr -d '\\n'",
" tmp=$(mktemp)",
" dd if=\"$target\" bs=\"$block_size\" skip=\"$block_index\" count=1 2>/dev/null > \"$tmp\"",
" bytes=$(wc -c < \"$tmp\" | tr -d '[:space:]')",
" digest=$(sha256_file \"$tmp\")",
` printf '${remoteReadBlockMarker} %s %s\\n' "$bytes" "$digest"`,
" base64 < \"$tmp\" | tr -d '\\n'",
" printf '\\n'",
" rm -f -- \"$tmp\"",
" ;;",
" write-b64-argv)",
" target=$1",