diff --git a/.changeset/strong-snails-stare.md b/.changeset/strong-snails-stare.md new file mode 100644 index 00000000000..2f472c510ab --- /dev/null +++ b/.changeset/strong-snails-stare.md @@ -0,0 +1,5 @@ +--- +"@remix-run/node": patch +--- + +Fix race condition with non-atomic writes for file storage. diff --git a/contributors.yml b/contributors.yml index c6b27d8c189..c73b9205937 100644 --- a/contributors.yml +++ b/contributors.yml @@ -351,6 +351,7 @@ - michaelgmcd - michaelhelvey - michaseel +- midgleyc - mikeybinnswebdesign - mirzafaizan - mitchelldirt diff --git a/packages/remix-node/package.json b/packages/remix-node/package.json index 7d2ef6a4067..b3d39dc8afa 100644 --- a/packages/remix-node/package.json +++ b/packages/remix-node/package.json @@ -25,11 +25,13 @@ "abort-controller": "^3.0.0", "cookie-signature": "^1.1.0", "source-map-support": "^0.5.21", - "stream-slice": "^0.1.2" + "stream-slice": "^0.1.2", + "write-file-atomic": "^5.0.0" }, "devDependencies": { "@types/cookie-signature": "^1.0.3", - "@types/source-map-support": "^0.5.4" + "@types/source-map-support": "^0.5.4", + "@types/write-file-atomic": "^4.0.0" }, "engines": { "node": ">=14" diff --git a/packages/remix-node/sessions/fileStorage.ts b/packages/remix-node/sessions/fileStorage.ts index 9f690801058..5c8e95e1cea 100644 --- a/packages/remix-node/sessions/fileStorage.ts +++ b/packages/remix-node/sessions/fileStorage.ts @@ -6,6 +6,7 @@ import type { SessionIdStorageStrategy, SessionData, } from "@remix-run/server-runtime"; +import writeFile from "write-file-atomic"; import { createSessionStorage } from "../implementations"; @@ -86,7 +87,7 @@ export function createFileSessionStorage({ let content = JSON.stringify({ data, expires }); let file = getFile(dir, id); await fsp.mkdir(path.dirname(file), { recursive: true }); - await fsp.writeFile(file, content, "utf-8"); + await writeFile(file, content, "utf-8"); }, async deleteData(id) { // Return early if the id is empty, otherwise we'll end up trying to diff --git a/yarn.lock b/yarn.lock index 0c5565c5c96..d2ee01a80e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3344,6 +3344,13 @@ resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== +"@types/write-file-atomic@^4.0.0": + version "4.0.0" + resolved "https://registry.npmjs.org/@types/write-file-atomic/-/write-file-atomic-4.0.0.tgz#ffcedcb1ae027e0a28ddfe218b72b3573797b5bc" + integrity sha512-piEKt2KKBUtye+feTlfdPjtW7uPFsAaLNX3/f6AJD+Y1T1YPTFwnqtlO9Y+gy9qGshrvxKa/Kay9vqbyVIuhwQ== + dependencies: + "@types/node" "*" + "@types/ws@^7.4.1": version "7.4.7" resolved "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz" @@ -11946,6 +11953,11 @@ signal-exit@^3.0.2, signal-exit@^3.0.3: resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz#96a61033896120ec9335d96851d902cc98f0ba2a" + integrity sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw== + simple-git@^3.16.0: version "3.16.0" resolved "https://registry.npmjs.org/simple-git/-/simple-git-3.16.0.tgz#421773e24680f5716999cc4a1d60127b4b6a9dec" @@ -13569,6 +13581,14 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" +write-file-atomic@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" + integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^4.0.1" + ws@^7.4.5, ws@^7.4.6: version "7.5.7" resolved "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz"