Skip to content

Commit e625f20

Browse files
committed
fix: allow subdirectory paths in loadStaticCaseData validation
The path traversal check rejected any filename containing `/`, which blocked curriculum stages from loading data files in subdirectories like `curriculum/first-sip/stage-1-goal.json`. Changed to only reject absolute paths (starting with `/`) while still blocking `..` traversal.
1 parent 4c9c4e8 commit e625f20

File tree

2 files changed

+13
-3
lines changed

2 files changed

+13
-3
lines changed

actions/case-data.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export async function loadStaticCaseData(
1111
caseFile: string
1212
): Promise<{ data: unknown } | { error: string }> {
1313
// Validate filename to prevent path traversal
14-
if (caseFile.includes("..") || caseFile.includes("/")) {
14+
if (caseFile.includes("..") || caseFile.startsWith("/")) {
1515
return { error: "Invalid case file name" };
1616
}
1717

src/__tests__/integration/server-actions.test.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,15 +123,25 @@ describe("loadStaticCaseData", () => {
123123
);
124124
});
125125

126-
it("rejects a filename containing a forward slash", async () => {
126+
it("rejects an absolute path starting with /", async () => {
127127
const { loadStaticCaseData } = await import("@/actions/case-data");
128128

129129
expectError(
130-
await loadStaticCaseData("subdir/file.json"),
130+
await loadStaticCaseData("/etc/passwd"),
131131
"Invalid case file name"
132132
);
133133
});
134134

135+
it("allows subdirectory paths with forward slashes", async () => {
136+
const { loadStaticCaseData } = await import("@/actions/case-data");
137+
138+
// Subdirectory paths are valid — curriculum stages use them
139+
const result = await loadStaticCaseData(
140+
"curriculum/first-sip/stage-1-goal.json"
141+
);
142+
expectSuccess(result);
143+
});
144+
135145
it("returns an error for a file that does not exist", async () => {
136146
const { loadStaticCaseData } = await import("@/actions/case-data");
137147
const result = await loadStaticCaseData("nonexistent-file-xyz.json");

0 commit comments

Comments
 (0)