Skip to content

Commit 0a47a0a

Browse files
committed
수정: Windows Execution Memory 경로 권한 정규화
Windows short path와 long path를 canonical filesystem path로 수렴시켜 승인 root를 정확히 비교한다. Evidence Pack 봉인을 복구하면서 존재하지 않는 경로와 root 밖 경로는 계속 거부한다. 검증: contracts 36 suites; test:mcp-product 20/20; npm test
1 parent b4a76dc commit 0a47a0a

4 files changed

Lines changed: 25 additions & 4 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ happen only on an explicit maintainer decision; the Unreleased section accumulat
7777
browser poll during Linux cleanup.
7878
- Native profile C sources and `Setup.local` inputs are pinned to LF checkout bytes. Their locked SHA-256 values
7979
now validate identically on Windows and Linux before the reproducible CPython WASI build starts.
80+
- Execution Memory import authority now compares canonical existing filesystem paths. Windows short and long
81+
aliases for the same approved root no longer reject Evidence Pack sealing, while missing paths and escapes
82+
remain outside the authority boundary.
8083
- The GitHub Pages demo now establishes cross-origin isolation through its versioned service-worker bootstrap
8184
before starting the owned kernel, while the no-header preflight continues to fail closed outside that entry.
8285

@@ -121,6 +124,8 @@ one-shot cursor와 pinned root에서 effect 없이 분기를 탐색하고 determ
121124
MCP fatal 종료는 보류 중인 page long poll을 명시적으로 닫고, 네이티브 프로필의 C와 Setup 입력은
122125
Windows와 Linux에서 같은 LF 바이트로 검증한다. GitHub Pages 데모는 versioned service worker로
123126
cross-origin isolation을 확보한 뒤 소유 커널을 시작한다.
127+
Execution Memory는 Windows의 short path와 long path를 실제 파일시스템 경로로 정규화해 같은 승인
128+
root로 판정하고, 존재하지 않는 경로와 root 밖 경로는 계속 거부한다.
124129

125130
## 0.0.21 - 2026-08-13
126131

‎scripts/executionMemory/executionMemoryTools.js‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// executionMemoryTools.js - Control과 MCP가 공유하는 Execution Memory operation과 handler.
2+
import { realpathSync } from "node:fs";
23
import { isAbsolute, relative, resolve } from "node:path";
34
import { createExecutionMemoryRegistry } from "./executionMemoryRegistry.js";
45
import { executionMemoryError } from "./executionMemoryCanonical.js";
@@ -72,9 +73,16 @@ function allowedPath(pathInput, roots, label) {
7273
if (typeof pathInput !== "string" || !isAbsolute(pathInput)) {
7374
throw executionMemoryError("EXECUTION_MEMORY_PATH", `${label} must be an absolute path`);
7475
}
75-
const target = resolve(pathInput);
76+
let target;
77+
try { target = realpathSync.native(resolve(pathInput)); }
78+
catch (error) {
79+
throw executionMemoryError("EXECUTION_MEMORY_PATH", `${label} must reference an existing path`);
80+
}
7681
if (!roots.some((root) => {
77-
const rel = relative(root, target);
82+
let canonicalRoot;
83+
try { canonicalRoot = realpathSync.native(resolve(root)); }
84+
catch (error) { return false; }
85+
const rel = relative(canonicalRoot, target);
7886
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
7987
})) throw executionMemoryError("EXECUTION_MEMORY_PATH", `${label} is outside configured roots`);
8088
return target;

‎tests/browser/installedMcpProduct.mjs‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// installedMcpProduct.mjs - packed pyproc-mcp command, Python machine, browser and artifacts in one gate.
22
import { createHash, generateKeyPairSync } from "node:crypto";
33
import { spawn } from "node:child_process";
4-
import { existsSync } from "node:fs";
4+
import { existsSync, realpathSync } from "node:fs";
55
import { createServer } from "node:http";
66
import { mkdir, rm, writeFile } from "node:fs/promises";
77
import { createInterface } from "node:readline";
@@ -62,7 +62,10 @@ const check = (name, pass, info = "") => {
6262

6363
const samePath = (left, right) => {
6464
if (typeof left !== "string" || typeof right !== "string") return false;
65-
const values = [left, right].map((value) => resolve(value));
65+
const values = [left, right].map((value) => {
66+
const absolute = resolve(value);
67+
return existsSync(absolute) ? realpathSync.native(absolute) : absolute;
68+
});
6669
return process.platform === "win32"
6770
? values[0].toLowerCase() === values[1].toLowerCase()
6871
: values[0] === values[1];

‎tests/contracts/executionMemory.mjs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createHash, sign } from "node:crypto";
2+
import { realpathSync } from "node:fs";
23
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
34
import { tmpdir } from "node:os";
45
import { join } from "node:path";
@@ -66,6 +67,10 @@ export async function assertExecutionMemoryContract() {
6667
permissionManifest: { pythonNetwork: "denied", browser: null }, importRoots: [targetRoot] });
6768
assert(handlerProduct.registry instanceof ExecutionMemoryRegistry,
6869
"비어 있지 않은 importRoots가 제품 handler 초기화를 깨뜨렸다");
70+
assert(handlerProduct.allowedImportPath(targetRoot, "fixtureRoot") === realpathSync.native(targetRoot),
71+
"existing import root의 canonical path가 보존되지 않았다");
72+
assert((await errorOf(() => handlerProduct.allowedImportPath(join(targetRoot, "missing"), "missing")))?.code
73+
=== "EXECUTION_MEMORY_PATH", "존재하지 않는 import path가 권한 경계를 통과했다");
6974
const source = await ExecutionMemoryRegistry.open({ root: sourceRoot, secretValues: ["fixture-secret"] });
7075
assert((await errorOf(async () => source.artifacts.captureMachineImage({
7176
bytes: await machineImage("fixture-secret"), machineId: "machine:secret", lifecycle: "portable",

0 commit comments

Comments
 (0)