@@ -7,6 +7,7 @@ import { OrganisationId, UserId } from "@cap/web-domain";
77import { eq } from "drizzle-orm" ;
88import { Effect , Option } from "effect" ;
99import { revalidatePath } from "next/cache" ;
10+ import * as path from "path" ;
1011import { runPromise } from "@/lib/server" ;
1112
1213export async function uploadImage (
@@ -29,7 +30,15 @@ export async function uploadImage(
2930 oldImageUrlOrKey . startsWith ( "https://" )
3031 ) {
3132 const url = new URL ( oldImageUrlOrKey ) ;
32- oldS3Key = url . pathname . substring ( 1 ) ;
33+ const raw = url . pathname . startsWith ( "/" )
34+ ? url . pathname . slice ( 1 )
35+ : url . pathname ;
36+ const decoded = decodeURIComponent ( raw ) ;
37+ const normalized = path . posix . normalize ( decoded ) ;
38+ if ( normalized . includes ( ".." ) ) {
39+ throw new Error ( "Invalid S3 key path" ) ;
40+ }
41+ oldS3Key = normalized ;
3342 }
3443
3544 // Only delete if it looks like the correct type of image key
@@ -38,7 +47,7 @@ export async function uploadImage(
3847 yield * bucket . deleteObject ( oldS3Key ) ;
3948 }
4049 } catch ( error ) {
41- console . error ( `Error deleting old ${ type } image from S3:` , error ) ;
50+ console . error ( `Error deleting old %s image from S3:` , type , error ) ;
4251 }
4352 }
4453
@@ -77,7 +86,7 @@ export async function uploadImage(
7786
7887 return { success : true , image : s3Key } as const ;
7988 } catch ( error ) {
80- console . error ( `Error uploading ${ type } image:` , error ) ;
89+ console . error ( `Error uploading %s image:` , type , error ) ;
8190 throw new Error ( error instanceof Error ? error . message : "Upload failed" ) ;
8291 }
8392}
0 commit comments