mirror of https://github.com/openclaw/openclaw.git
refactor(core): dedupe final pairing and sandbox media clones
This commit is contained in:
parent
453a1c179d
commit
5897eed6e9
|
|
@ -26,22 +26,40 @@ afterEach(() => {
|
|||
childProcessMocks.spawn.mockClear();
|
||||
});
|
||||
|
||||
function setupSandboxWorkspace(home: string): {
|
||||
cfg: ReturnType<typeof createSandboxMediaStageConfig>;
|
||||
workspaceDir: string;
|
||||
sandboxDir: string;
|
||||
} {
|
||||
const cfg = createSandboxMediaStageConfig(home);
|
||||
const workspaceDir = join(home, "openclaw");
|
||||
const sandboxDir = join(home, "sandboxes", "session");
|
||||
vi.mocked(ensureSandboxWorkspaceForSession).mockResolvedValue({
|
||||
workspaceDir: sandboxDir,
|
||||
containerWorkdir: "/work",
|
||||
});
|
||||
return { cfg, workspaceDir, sandboxDir };
|
||||
}
|
||||
|
||||
async function writeInboundMedia(
|
||||
home: string,
|
||||
fileName: string,
|
||||
payload: string | Buffer,
|
||||
): Promise<string> {
|
||||
const inboundDir = join(home, ".openclaw", "media", "inbound");
|
||||
await fs.mkdir(inboundDir, { recursive: true });
|
||||
const mediaPath = join(inboundDir, fileName);
|
||||
await fs.writeFile(mediaPath, payload);
|
||||
return mediaPath;
|
||||
}
|
||||
|
||||
describe("stageSandboxMedia", () => {
|
||||
it("stages allowed media and blocks unsafe paths", async () => {
|
||||
await withSandboxMediaTempHome("openclaw-triggers-", async (home) => {
|
||||
const cfg = createSandboxMediaStageConfig(home);
|
||||
const workspaceDir = join(home, "openclaw");
|
||||
const sandboxDir = join(home, "sandboxes", "session");
|
||||
vi.mocked(ensureSandboxWorkspaceForSession).mockResolvedValue({
|
||||
workspaceDir: sandboxDir,
|
||||
containerWorkdir: "/work",
|
||||
});
|
||||
const { cfg, workspaceDir, sandboxDir } = setupSandboxWorkspace(home);
|
||||
|
||||
{
|
||||
const inboundDir = join(home, ".openclaw", "media", "inbound");
|
||||
await fs.mkdir(inboundDir, { recursive: true });
|
||||
const mediaPath = join(inboundDir, "photo.jpg");
|
||||
await fs.writeFile(mediaPath, "test");
|
||||
const mediaPath = await writeInboundMedia(home, "photo.jpg", "test");
|
||||
const { ctx, sessionCtx } = createSandboxMediaContexts(mediaPath);
|
||||
|
||||
await stageSandboxMedia({
|
||||
|
|
@ -105,18 +123,9 @@ describe("stageSandboxMedia", () => {
|
|||
|
||||
it("blocks destination symlink escapes when staging into sandbox workspace", async () => {
|
||||
await withSandboxMediaTempHome("openclaw-triggers-", async (home) => {
|
||||
const cfg = createSandboxMediaStageConfig(home);
|
||||
const workspaceDir = join(home, "openclaw");
|
||||
const sandboxDir = join(home, "sandboxes", "session");
|
||||
vi.mocked(ensureSandboxWorkspaceForSession).mockResolvedValue({
|
||||
workspaceDir: sandboxDir,
|
||||
containerWorkdir: "/work",
|
||||
});
|
||||
const { cfg, workspaceDir, sandboxDir } = setupSandboxWorkspace(home);
|
||||
|
||||
const inboundDir = join(home, ".openclaw", "media", "inbound");
|
||||
await fs.mkdir(inboundDir, { recursive: true });
|
||||
const mediaPath = join(inboundDir, "payload.txt");
|
||||
await fs.writeFile(mediaPath, "PAYLOAD");
|
||||
const mediaPath = await writeInboundMedia(home, "payload.txt", "PAYLOAD");
|
||||
|
||||
const outsideDir = join(home, "outside");
|
||||
const outsideInboundDir = join(outsideDir, "inbound");
|
||||
|
|
@ -145,18 +154,13 @@ describe("stageSandboxMedia", () => {
|
|||
|
||||
it("skips oversized media staging and keeps original media paths", async () => {
|
||||
await withSandboxMediaTempHome("openclaw-triggers-", async (home) => {
|
||||
const cfg = createSandboxMediaStageConfig(home);
|
||||
const workspaceDir = join(home, "openclaw");
|
||||
const sandboxDir = join(home, "sandboxes", "session");
|
||||
vi.mocked(ensureSandboxWorkspaceForSession).mockResolvedValue({
|
||||
workspaceDir: sandboxDir,
|
||||
containerWorkdir: "/work",
|
||||
});
|
||||
const { cfg, workspaceDir, sandboxDir } = setupSandboxWorkspace(home);
|
||||
|
||||
const inboundDir = join(home, ".openclaw", "media", "inbound");
|
||||
await fs.mkdir(inboundDir, { recursive: true });
|
||||
const mediaPath = join(inboundDir, "oversized.bin");
|
||||
await fs.writeFile(mediaPath, Buffer.alloc(MEDIA_MAX_BYTES + 1, 0x41));
|
||||
const mediaPath = await writeInboundMedia(
|
||||
home,
|
||||
"oversized.bin",
|
||||
Buffer.alloc(MEDIA_MAX_BYTES + 1, 0x41),
|
||||
);
|
||||
|
||||
const { ctx, sessionCtx } = createSandboxMediaContexts(mediaPath);
|
||||
await stageSandboxMedia({
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ type AllowFromReadCacheEntry = {
|
|||
size: number | null;
|
||||
entries: string[];
|
||||
};
|
||||
type AllowFromStatLike = { mtimeMs: number; size: number } | null;
|
||||
|
||||
const allowFromReadCache = new Map<string, AllowFromReadCacheEntry>();
|
||||
|
||||
|
|
@ -321,6 +322,31 @@ function resolveAllowFromReadCacheHit(params: {
|
|||
return cloneAllowFromCacheEntry(cached);
|
||||
}
|
||||
|
||||
function resolveAllowFromReadCacheOrMissing(
|
||||
filePath: string,
|
||||
stat: AllowFromStatLike,
|
||||
): { entries: string[]; exists: boolean } | null {
|
||||
const cached = resolveAllowFromReadCacheHit({
|
||||
filePath,
|
||||
exists: Boolean(stat),
|
||||
mtimeMs: stat?.mtimeMs ?? null,
|
||||
size: stat?.size ?? null,
|
||||
});
|
||||
if (cached) {
|
||||
return { entries: cached.entries, exists: cached.exists };
|
||||
}
|
||||
if (!stat) {
|
||||
setAllowFromReadCache(filePath, {
|
||||
exists: false,
|
||||
mtimeMs: null,
|
||||
size: null,
|
||||
entries: [],
|
||||
});
|
||||
return { entries: [], exists: false };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function readAllowFromStateForPathWithExists(
|
||||
channel: PairingChannel,
|
||||
filePath: string,
|
||||
|
|
@ -335,24 +361,9 @@ async function readAllowFromStateForPathWithExists(
|
|||
}
|
||||
}
|
||||
|
||||
const cached = resolveAllowFromReadCacheHit({
|
||||
filePath,
|
||||
exists: Boolean(stat),
|
||||
mtimeMs: stat?.mtimeMs ?? null,
|
||||
size: stat?.size ?? null,
|
||||
});
|
||||
if (cached) {
|
||||
return { entries: cached.entries, exists: cached.exists };
|
||||
}
|
||||
|
||||
if (!stat) {
|
||||
setAllowFromReadCache(filePath, {
|
||||
exists: false,
|
||||
mtimeMs: null,
|
||||
size: null,
|
||||
entries: [],
|
||||
});
|
||||
return { entries: [], exists: false };
|
||||
const cachedOrMissing = resolveAllowFromReadCacheOrMissing(filePath, stat);
|
||||
if (cachedOrMissing) {
|
||||
return cachedOrMissing;
|
||||
}
|
||||
|
||||
const { value, exists } = await readJsonFile<AllowFromStore>(filePath, {
|
||||
|
|
@ -387,24 +398,9 @@ function readAllowFromStateForPathSyncWithExists(
|
|||
}
|
||||
}
|
||||
|
||||
const cached = resolveAllowFromReadCacheHit({
|
||||
filePath,
|
||||
exists: Boolean(stat),
|
||||
mtimeMs: stat?.mtimeMs ?? null,
|
||||
size: stat?.size ?? null,
|
||||
});
|
||||
if (cached) {
|
||||
return { entries: cached.entries, exists: cached.exists };
|
||||
}
|
||||
|
||||
if (!stat) {
|
||||
setAllowFromReadCache(filePath, {
|
||||
exists: false,
|
||||
mtimeMs: null,
|
||||
size: null,
|
||||
entries: [],
|
||||
});
|
||||
return { entries: [], exists: false };
|
||||
const cachedOrMissing = resolveAllowFromReadCacheOrMissing(filePath, stat);
|
||||
if (cachedOrMissing) {
|
||||
return cachedOrMissing;
|
||||
}
|
||||
|
||||
let raw = "";
|
||||
|
|
|
|||
Loading…
Reference in New Issue