From 1b8d8e670d8bba87ce113582297e4ef7ca0266af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 May 2026 22:08:51 +0000 Subject: [PATCH] fix: enforce 0o600 permissions on sensitive files by unlinking before write Unlink any existing file before writing in writeFileWithMode so the mode option is always applied on fresh file creation (Node.js ignores mode for existing files). This prevents inheriting wrong permissions on Docker bind-mount filesystems such as Synology NAS where ACLs can cause SSH key files to be created with 755 instead of 600. The fs.unlink error is only swallowed for ENOENT (file doesn't exist); other errors (e.g. EPERM) propagate as before. The chmod call is kept as an additional safety net against umask effects. A test for the new-file creation case is added alongside the existing existing-file rewrite test. Agent-Logs-Url: https://github.com/mowdep/zerobyte/sessions/2a4ab129-9668-402e-8687-d792f9b9e704 Co-authored-by: mowdep <10937987+mowdep@users.noreply.github.com> --- packages/core/src/node/__tests__/fs.test.ts | 12 ++++++++++++ packages/core/src/node/fs.ts | 7 +++++++ 2 files changed, 19 insertions(+) diff --git a/packages/core/src/node/__tests__/fs.test.ts b/packages/core/src/node/__tests__/fs.test.ts index 1aaed3d4..79b20bbd 100644 --- a/packages/core/src/node/__tests__/fs.test.ts +++ b/packages/core/src/node/__tests__/fs.test.ts @@ -16,6 +16,18 @@ afterEach(async () => { }); describe("writeFileWithMode", () => { + test("applies the requested mode when creating a new file", async () => { + const tempDirectory = await fs.mkdtemp(path.join(os.tmpdir(), "zerobyte-write-file-with-mode-")); + tempDirectories.add(tempDirectory); + + const filePath = path.join(tempDirectory, "identity"); + + await writeFileWithMode(filePath, "content", FILE_MODES.ownerReadWrite); + + expect(await fs.readFile(filePath, "utf8")).toBe("content"); + expect((await fs.stat(filePath)).mode & 0o777).toBe(0o600); + }); + test("applies the requested mode even when rewriting an existing file", async () => { const tempDirectory = await fs.mkdtemp(path.join(os.tmpdir(), "zerobyte-write-file-with-mode-")); tempDirectories.add(tempDirectory); diff --git a/packages/core/src/node/fs.ts b/packages/core/src/node/fs.ts index 1b5b4b06..de8b9d1b 100644 --- a/packages/core/src/node/fs.ts +++ b/packages/core/src/node/fs.ts @@ -7,6 +7,13 @@ export const FILE_MODES = { type FileMode = (typeof FILE_MODES)[keyof typeof FILE_MODES]; export const writeFileWithMode = async (filePath: string, data: string, mode: FileMode) => { + // Remove any existing file first so the mode option on writeFile is always applied + // on a fresh file creation (mode is ignored for existing files). This also avoids + // inheriting incorrect permissions from a previously-created file on filesystems + // where chmod may not behave as expected (e.g. Docker bind-mounts on some NAS systems). + await fs.unlink(filePath).catch((error: NodeJS.ErrnoException) => { + if (error.code !== "ENOENT") throw error; + }); await fs.writeFile(filePath, data, { mode }); await fs.chmod(filePath, mode); };