Skip to content

Commit 64d88de

Browse files
authored
Validate session id in file session storage (#10806)
1 parent ad37899 commit 64d88de

4 files changed

Lines changed: 120 additions & 48 deletions

File tree

CHANGELOG.md

Lines changed: 57 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,162 +13,164 @@ We manage release notes in this file instead of the paginated GitHub Releases Pa
1313
<summary>Table of Contents</summary>
1414

1515
- [Remix v2 Releases](#remix-v2-releases)
16-
- [v2.17.1](#v2171)
16+
- [v2.17.2](#v2172)
1717
- [Patch Changes](#patch-changes)
18+
- [v2.17.1](#v2171)
19+
- [Patch Changes](#patch-changes-1)
1820
- [v2.17.0](#v2170)
1921
- [Minor Changes](#minor-changes)
2022
- [Changes by Package](#changes-by-package)
2123
- [v2.16.8](#v2168)
22-
- [Patch Changes](#patch-changes-1)
23-
- [v2.16.7](#v2167)
2424
- [Patch Changes](#patch-changes-2)
25-
- [v2.16.6](#v2166)
25+
- [v2.16.7](#v2167)
2626
- [Patch Changes](#patch-changes-3)
27-
- [v2.16.5](#v2165)
27+
- [v2.16.6](#v2166)
2828
- [Patch Changes](#patch-changes-4)
29-
- [v2.16.4](#v2164)
29+
- [v2.16.5](#v2165)
3030
- [Patch Changes](#patch-changes-5)
31+
- [v2.16.4](#v2164)
32+
- [Patch Changes](#patch-changes-6)
3133
- [v2.16.3](#v2163)
3234
- [Security Notice](#security-notice)
33-
- [Patch Changes](#patch-changes-6)
34-
- [v2.16.2](#v2162)
3535
- [Patch Changes](#patch-changes-7)
36-
- [v2.16.1](#v2161)
36+
- [v2.16.2](#v2162)
3737
- [Patch Changes](#patch-changes-8)
38+
- [v2.16.1](#v2161)
39+
- [Patch Changes](#patch-changes-9)
3840
- [v2.16.0](#v2160)
3941
- [Minor Changes](#minor-changes-1)
40-
- [Patch Changes](#patch-changes-9)
42+
- [Patch Changes](#patch-changes-10)
4143
- [Updated Dependencies](#updated-dependencies)
4244
- [v2.15.3](#v2153)
43-
- [Patch Changes](#patch-changes-10)
45+
- [Patch Changes](#patch-changes-11)
4446
- [Updated Dependencies](#updated-dependencies-1)
4547
- [v2.15.2](#v2152)
46-
- [Patch Changes](#patch-changes-11)
48+
- [Patch Changes](#patch-changes-12)
4749
- [Updated Dependencies](#updated-dependencies-2)
4850
- [v2.15.1](#v2151)
49-
- [Patch Changes](#patch-changes-12)
50-
- [v2.15.0](#v2150)
5151
- [Patch Changes](#patch-changes-13)
52+
- [v2.15.0](#v2150)
53+
- [Patch Changes](#patch-changes-14)
5254
- [v2.14.0](#v2140)
5355
- [Minor Changes](#minor-changes-2)
54-
- [Patch Changes](#patch-changes-14)
56+
- [Patch Changes](#patch-changes-15)
5557
- [Updated Dependencies](#updated-dependencies-3)
5658
- [Changes by Package](#changes-by-package-1)
5759
- [v2.13.1](#v2131)
58-
- [Patch Changes](#patch-changes-15)
60+
- [Patch Changes](#patch-changes-16)
5961
- [v2.13.0](#v2130)
6062
- [What's Changed](#whats-changed)
6163
- [Stabilized APIs](#stabilized-apis)
6264
- [Minor Changes](#minor-changes-3)
63-
- [Patch Changes](#patch-changes-16)
65+
- [Patch Changes](#patch-changes-17)
6466
- [Updated Dependencies](#updated-dependencies-4)
6567
- [Changes by Package](#changes-by-package-2)
6668
- [v2.12.1](#v2121)
67-
- [Patch Changes](#patch-changes-17)
69+
- [Patch Changes](#patch-changes-18)
6870
- [Changes by Package](#changes-by-package-3)
6971
- [v2.12.0](#v2120)
7072
- [What's Changed](#whats-changed-1)
7173
- [Future Flag for Automatic Dependency Optimization (unstable)](#future-flag-for-automatic-dependency-optimization-unstable)
7274
- [Improved Single Fetch Type Safety (unstable)](#improved-single-fetch-type-safety-unstable)
7375
- [Updates to Single Fetch Revalidation Behavior (unstable)](#updates-to-single-fetch-revalidation-behavior-unstable)
7476
- [Minor Changes](#minor-changes-4)
75-
- [Patch Changes](#patch-changes-18)
77+
- [Patch Changes](#patch-changes-19)
7678
- [Updated Dependencies](#updated-dependencies-5)
7779
- [Changes by Package](#changes-by-package-4)
7880
- [v2.11.2](#v2112)
79-
- [Patch Changes](#patch-changes-19)
81+
- [Patch Changes](#patch-changes-20)
8082
- [Updated Dependencies](#updated-dependencies-6)
8183
- [Changes by Package](#changes-by-package-5)
8284
- [v2.11.1](#v2111)
83-
- [Patch Changes](#patch-changes-20)
85+
- [Patch Changes](#patch-changes-21)
8486
- [Changes by Package](#changes-by-package-6)
8587
- [v2.11.0](#v2110)
8688
- [What's Changed](#whats-changed-2)
8789
- [Renamed `unstable_fogOfWar` future flag to `unstable_lazyRouteDiscovery` (unstable)](#renamed-unstable_fogofwar-future-flag-to-unstable_lazyroutediscovery-unstable)
8890
- [Removed `response` stub in Single Fetch (unstable)](#removed-response-stub-in-single-fetch-unstable)
8991
- [Minor Changes](#minor-changes-5)
90-
- [Patch Changes](#patch-changes-21)
92+
- [Patch Changes](#patch-changes-22)
9193
- [Updated Dependencies](#updated-dependencies-7)
9294
- [Changes by Package](#changes-by-package-7)
9395
- [v2.10.3](#v2103)
94-
- [Patch Changes](#patch-changes-22)
96+
- [Patch Changes](#patch-changes-23)
9597
- [Updated Dependencies](#updated-dependencies-8)
9698
- [Changes by Package](#changes-by-package-8)
9799
- [v2.10.2](#v2102)
98-
- [Patch Changes](#patch-changes-23)
100+
- [Patch Changes](#patch-changes-24)
99101
- [Changes by Package](#changes-by-package-9)
100102
- [v2.10.1](#v2101)
101-
- [Patch Changes](#patch-changes-24)
103+
- [Patch Changes](#patch-changes-25)
102104
- [Updated Dependencies](#updated-dependencies-9)
103105
- [Changes by Package](#changes-by-package-10)
104106
- [v2.10.0](#v2100)
105107
- [What's Changed](#whats-changed-3)
106108
- [Lazy Route Discovery (a.k.a. "Fog of War")](#lazy-route-discovery-aka-fog-of-war)
107109
- [Minor Changes](#minor-changes-6)
108-
- [Patch Changes](#patch-changes-25)
110+
- [Patch Changes](#patch-changes-26)
109111
- [Updated Dependencies](#updated-dependencies-10)
110112
- [Changes by Package](#changes-by-package-11)
111113
- [v2.9.2](#v292)
112114
- [What's Changed](#whats-changed-4)
113115
- [Updated Type-Safety for Single Fetch](#updated-type-safety-for-single-fetch)
114-
- [Patch Changes](#patch-changes-26)
116+
- [Patch Changes](#patch-changes-27)
115117
- [Updated Dependencies](#updated-dependencies-11)
116118
- [Changes by Package](#changes-by-package-12)
117119
- [v2.9.1](#v291)
118-
- [Patch Changes](#patch-changes-27)
120+
- [Patch Changes](#patch-changes-28)
119121
- [Changes by Package](#changes-by-package-13)
120122
- [v2.9.0](#v290)
121123
- [What's Changed](#whats-changed-5)
122124
- [Single Fetch (unstable)](#single-fetch-unstable)
123125
- [Undici](#undici)
124126
- [Minor Changes](#minor-changes-7)
125-
- [Patch Changes](#patch-changes-28)
127+
- [Patch Changes](#patch-changes-29)
126128
- [Updated Dependencies](#updated-dependencies-12)
127129
- [Changes by Package](#changes-by-package-14)
128130
- [v2.8.1](#v281)
129-
- [Patch Changes](#patch-changes-29)
131+
- [Patch Changes](#patch-changes-30)
130132
- [Updated Dependencies](#updated-dependencies-13)
131133
- [Changes by Package](#changes-by-package-15)
132134
- [v2.8.0](#v280)
133135
- [Minor Changes](#minor-changes-8)
134-
- [Patch Changes](#patch-changes-30)
136+
- [Patch Changes](#patch-changes-31)
135137
- [Updated Dependencies](#updated-dependencies-14)
136138
- [Changes by Package](#changes-by-package-16)
137139
- [2.7.2](#272)
138-
- [Patch Changes](#patch-changes-31)
139-
- [2.7.1](#271)
140140
- [Patch Changes](#patch-changes-32)
141+
- [2.7.1](#271)
142+
- [Patch Changes](#patch-changes-33)
141143
- [v2.7.0](#v270)
142144
- [What's Changed](#whats-changed-6)
143145
- [Stabilized Vite Plugin](#stabilized-vite-plugin)
144146
- [New `Layout` Export](#new-layout-export)
145147
- [Basename support](#basename-support)
146148
- [Cloudflare Proxy as a Vite Plugin](#cloudflare-proxy-as-a-vite-plugin)
147149
- [Minor Changes](#minor-changes-9)
148-
- [Patch Changes](#patch-changes-33)
150+
- [Patch Changes](#patch-changes-34)
149151
- [Updated Dependencies](#updated-dependencies-15)
150152
- [Changes by Package](#changes-by-package-17)
151153
- [v2.6.0](#v260)
152154
- [What's Changed](#whats-changed-7)
153155
- [Unstable Vite Plugin updates](#unstable-vite-plugin-updates)
154156
- [Minor Changes](#minor-changes-10)
155-
- [Patch Changes](#patch-changes-34)
157+
- [Patch Changes](#patch-changes-35)
156158
- [Updated Dependencies](#updated-dependencies-16)
157159
- [Changes by Package](#changes-by-package-18)
158160
- [v2.5.1](#v251)
159-
- [Patch Changes](#patch-changes-35)
161+
- [Patch Changes](#patch-changes-36)
160162
- [Updated Dependencies](#updated-dependencies-17)
161163
- [Changes by Package](#changes-by-package-19)
162164
- [v2.5.0](#v250)
163165
- [What's Changed](#whats-changed-8)
164166
- [SPA Mode (unstable)](#spa-mode-unstable)
165167
- [Server Bundles (unstable)](#server-bundles-unstable)
166168
- [Minor Changes](#minor-changes-11)
167-
- [Patch Changes](#patch-changes-36)
169+
- [Patch Changes](#patch-changes-37)
168170
- [Updated Dependencies](#updated-dependencies-18)
169171
- [Changes by Package](#changes-by-package-20)
170172
- [v2.4.1](#v241)
171-
- [Patch Changes](#patch-changes-37)
173+
- [Patch Changes](#patch-changes-38)
172174
- [Updated Dependencies](#updated-dependencies-19)
173175
- [Changes by Package](#changes-by-package-21)
174176
- [v2.4.0](#v240)
@@ -177,19 +179,19 @@ We manage release notes in this file instead of the paginated GitHub Releases Pa
177179
- [`future.v3_relativeSplatPath`](#futurev3_relativesplatpath)
178180
- [Vite Updates (Unstable)](#vite-updates-unstable)
179181
- [Minor Changes](#minor-changes-12)
180-
- [Patch Changes](#patch-changes-38)
182+
- [Patch Changes](#patch-changes-39)
181183
- [Updated Dependencies](#updated-dependencies-20)
182184
- [Changes by Package](#changes-by-package-22)
183185
- [v2.3.1](#v231)
184-
- [Patch Changes](#patch-changes-39)
186+
- [Patch Changes](#patch-changes-40)
185187
- [Updated Dependencies](#updated-dependencies-21)
186188
- [Changes by Package](#changes-by-package-23)
187189
- [v2.3.0](#v230)
188190
- [What's Changed](#whats-changed-10)
189191
- [Stabilized `useBlocker`](#stabilized-useblocker)
190192
- [`unstable_flushSync` API](#unstable_flushsync-api)
191193
- [Minor Changes](#minor-changes-13)
192-
- [Patch Changes](#patch-changes-40)
194+
- [Patch Changes](#patch-changes-41)
193195
- [Updated Dependencies](#updated-dependencies-22)
194196
- [Changes by Package](#changes-by-package-24)
195197
- [v2.2.0](#v220)
@@ -198,19 +200,19 @@ We manage release notes in this file instead of the paginated GitHub Releases Pa
198200
- [New Fetcher APIs](#new-fetcher-apis)
199201
- [Persistence Future Flag](#persistence-future-flag)
200202
- [Minor Changes](#minor-changes-14)
201-
- [Patch Changes](#patch-changes-41)
203+
- [Patch Changes](#patch-changes-42)
202204
- [Updated Dependencies](#updated-dependencies-23)
203205
- [Changes by Package](#changes-by-package-25)
204206
- [v2.1.0](#v210)
205207
- [What's Changed](#whats-changed-12)
206208
- [View Transitions](#view-transitions)
207209
- [Stable `createRemixStub`](#stable-createremixstub)
208210
- [Minor Changes](#minor-changes-15)
209-
- [Patch Changes](#patch-changes-42)
211+
- [Patch Changes](#patch-changes-43)
210212
- [Updated Dependencies](#updated-dependencies-24)
211213
- [Changes by Package](#changes-by-package-26)
212214
- [v2.0.1](#v201)
213-
- [Patch Changes](#patch-changes-43)
215+
- [Patch Changes](#patch-changes-44)
214216
- [Changes by Package 🔗](#changes-by-package-)
215217
- [v2.0.0](#v200)
216218
- [Breaking Changes](#breaking-changes)
@@ -242,6 +244,17 @@ Date: YYYY-MM-DD
242244
243245
-->
244246

247+
## v2.17.2
248+
249+
Date: 2025-10-30
250+
251+
### Patch Changes
252+
253+
- `@remix-run/deno` - Validate format of incoming session ids in `createFileSessionStorage`
254+
- `@remix-run/node` - Validate format of incoming session ids in `createFileSessionStorage`
255+
256+
**Full Changelog**: [`v2.17.1...v2.17.2`](https://github.com/remix-run/remix/compare/remix@2.17.1...remix@2.17.2)
257+
245258
## v2.17.1
246259

247260
Date: 2025-09-17

packages/remix-deno/sessions/fileStorage.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ export function createFileSessionStorage<Data = SessionData, FlashData = Data>({
5050

5151
try {
5252
const file = getFile(dir, id);
53+
if (!file) {
54+
throw new Error("Error generating session");
55+
}
5356
const exists = await Deno.stat(file)
5457
.then((s) => s.isFile)
5558
.catch(() => false);
@@ -69,6 +72,9 @@ export function createFileSessionStorage<Data = SessionData, FlashData = Data>({
6972
readData: async (id) => {
7073
try {
7174
const file = getFile(dir, id);
75+
if (!file) {
76+
return null;
77+
}
7278
const content = JSON.parse(await Deno.readTextFile(file));
7379
const data = content.data;
7480
const expires = typeof content.expires === "string"
@@ -91,20 +97,31 @@ export function createFileSessionStorage<Data = SessionData, FlashData = Data>({
9197
updateData: async (id, data, expires) => {
9298
const content = JSON.stringify({ data, expires });
9399
const file = getFile(dir, id);
100+
if (!file) {
101+
return;
102+
}
94103
await Deno.mkdir(path.dirname(file), { recursive: true }).catch(() => {});
95104
await Deno.writeTextFile(file, content);
96105
},
97106
deleteData: async (id) => {
98107
try {
99-
await Deno.remove(getFile(dir, id));
108+
const file = getFile(dir, id);
109+
if (!file) {
110+
return;
111+
}
112+
await Deno.remove(file);
100113
} catch (error) {
101114
if (error.code !== "ENOENT") throw error;
102115
}
103116
},
104117
});
105118
}
106119

107-
function getFile(dir: string, id: string): string {
120+
function getFile(dir: string, id: string): string | null {
121+
if (!/^[0-9a-f]{16}$/i.test(id)) {
122+
return null;
123+
}
124+
108125
// Divide the session id up into a directory (first 2 bytes) and filename
109126
// (remaining 6 bytes) to reduce the chance of having very large directories,
110127
// which should speed up file access. This is a maximum of 2^16 directories,

packages/remix-node/__tests__/sessions-test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,31 @@ describe("File session storage", () => {
5151
expect(session.get("user")).toBeUndefined();
5252
});
5353

54+
it("returns an empty session for invalid session ids", async () => {
55+
let spy = jest.spyOn(console, "warn").mockImplementation(() => {});
56+
let { getSession, commitSession } = createFileSessionStorage({
57+
dir,
58+
});
59+
60+
let cookie = `__session=${btoa(JSON.stringify("0123456789abcdef"))}`;
61+
let session = await getSession(cookie);
62+
session.set("user", "mjackson");
63+
expect(session.get("user")).toBe("mjackson");
64+
let setCookie = await commitSession(session);
65+
session = await getSession(getCookieFromSetCookie(setCookie));
66+
expect(session.get("user")).toBe("mjackson");
67+
68+
cookie = `__session=${btoa(JSON.stringify("0123456789abcdeg"))}`;
69+
session = await getSession(cookie);
70+
session.set("user", "mjackson");
71+
expect(session.get("user")).toBe("mjackson");
72+
setCookie = await commitSession(session);
73+
session = await getSession(getCookieFromSetCookie(setCookie));
74+
expect(session.get("user")).toBeUndefined();
75+
76+
spy.mockRestore();
77+
});
78+
5479
it("doesn't destroy the entire session directory when destroying an empty file session", async () => {
5580
let { getSession, destroySession } = createFileSessionStorage({
5681
dir,

0 commit comments

Comments
 (0)