From aefc483932a2a2613a522154fa66d223f209a4e1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 22:39:59 +0000 Subject: [PATCH 1/2] Initial plan From c53129a5d2148759f2f449c66de3ced9f9c07e1f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 14 Jan 2026 22:50:26 +0000 Subject: [PATCH 2/2] Fix TOCTOU security vulnerability in changeset.js Replace fs.existsSync check with file descriptor-based operations to eliminate Time-of-Check-Time-of-Use (TOCTOU) vulnerabilities in: - updateChangelog function (line 463 - primary security issue) - readChangesets function (line 114 - additional TOCTOU prevention) Changes: - Use fs.openSync with O_RDONLY for reading CHANGELOG.md - Use fs.openSync with O_WRONLY | O_CREAT | O_TRUNC for writing CHANGELOG.md - Properly close file descriptors after operations - Handle ENOENT errors to maintain same behavior as existsSync - Use try-catch for directory reading instead of existence check This eliminates the window of vulnerability where an attacker could modify files between existence checks and file operations. Co-authored-by: eaftan <4733401+eaftan@users.noreply.github.com> --- scripts/changeset.js | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/scripts/changeset.js b/scripts/changeset.js index edaf657de8a..e26aaca5fed 100755 --- a/scripts/changeset.js +++ b/scripts/changeset.js @@ -111,11 +111,17 @@ function parseChangesetFile(filePath) { function readChangesets() { const changesetDir = ".changeset"; - if (!fs.existsSync(changesetDir)) { - throw new Error("Changeset directory not found: .changeset/"); + // Try to read directory without checking existence first (avoids TOCTOU) + let entries; + try { + entries = fs.readdirSync(changesetDir); + } catch (error) { + if (error.code === "ENOENT") { + throw new Error("Changeset directory not found: .changeset/"); + } + throw error; } - const entries = fs.readdirSync(changesetDir); const changesets = []; for (const entry of entries) { @@ -388,11 +394,21 @@ function updateChangelog(version, changesets, dryRun = false) { const changelogPath = "CHANGELOG.md"; // Read existing changelog or create header + // Use file descriptor to avoid TOCTOU vulnerability let existingContent = ""; - if (fs.existsSync(changelogPath)) { - existingContent = fs.readFileSync(changelogPath, "utf8"); - } else { - existingContent = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n"; + let fd; + try { + // Try to open existing file for reading + fd = fs.openSync(changelogPath, fs.constants.O_RDONLY); + existingContent = fs.readFileSync(fd, "utf8"); + fs.closeSync(fd); + } catch (error) { + // File doesn't exist or can't be read, use default header + if (error.code === "ENOENT") { + existingContent = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n"; + } else { + throw error; + } } // Build new entry @@ -459,8 +475,14 @@ function updateChangelog(version, changesets, dryRun = false) { return newEntry; } - // Write updated changelog - fs.writeFileSync(changelogPath, updatedContent, "utf8"); + // Write updated changelog using file descriptor to avoid TOCTOU vulnerability + try { + fd = fs.openSync(changelogPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC, 0o644); + fs.writeFileSync(fd, updatedContent, "utf8"); + fs.closeSync(fd); + } catch (error) { + throw new Error(`Failed to write CHANGELOG.md: ${error.message}`); + } return newEntry; }