diff --git a/src/components/video-editor/projectPersistence.ts b/src/components/video-editor/projectPersistence.ts index 7c963d7..7259c1e 100644 --- a/src/components/video-editor/projectPersistence.ts +++ b/src/components/video-editor/projectPersistence.ts @@ -56,8 +56,6 @@ function normalizeWallpaperValue(value: string): string { return CANONICAL_WALLPAPERS.has(canonical) ? canonical : DEFAULT_WALLPAPER; } -export { WALLPAPER_PATHS }; - export const PROJECT_VERSION = 2; export interface ProjectEditorState { diff --git a/src/lib/wallpaper.test.ts b/src/lib/wallpaper.test.ts index 6e1b74a..02596aa 100644 --- a/src/lib/wallpaper.test.ts +++ b/src/lib/wallpaper.test.ts @@ -5,6 +5,7 @@ import { classifyWallpaper, DEFAULT_WALLPAPER, resolveImageWallpaperUrl, + UnsafeImagePrefixError, WALLPAPER_COUNT, WALLPAPER_PATHS, } from "./wallpaper"; @@ -170,8 +171,14 @@ describe("resolveImageWallpaperUrl", () => { expect(resolveImageWallpaperUrl("/wallpapers/my image.jpg")).toBe("/wallpapers/my%20image.jpg"); }); - it("rejects image paths outside /wallpapers/", () => { - expect(() => resolveImageWallpaperUrl("/etc/passwd")).toThrow(BackgroundLoadError); + it("rejects image paths outside /wallpapers/ with UnsafeImagePrefixError as cause", () => { + try { + resolveImageWallpaperUrl("/etc/passwd"); + expect.fail("should have thrown"); + } catch (err) { + if (!(err instanceof BackgroundLoadError)) throw err; + expect(err.cause).toBeInstanceOf(UnsafeImagePrefixError); + } }); it("wraps traversal attempts in BackgroundLoadError (preserves UnsafeAssetPathError as cause)", () => { @@ -179,8 +186,8 @@ describe("resolveImageWallpaperUrl", () => { resolveImageWallpaperUrl("/wallpapers/../etc/passwd"); expect.fail("should have thrown"); } catch (err) { - expect(err).toBeInstanceOf(BackgroundLoadError); - expect((err as BackgroundLoadError).cause).toBeInstanceOf(UnsafeAssetPathError); + if (!(err instanceof BackgroundLoadError)) throw err; + expect(err.cause).toBeInstanceOf(UnsafeAssetPathError); } }); @@ -189,8 +196,8 @@ describe("resolveImageWallpaperUrl", () => { resolveImageWallpaperUrl("/wallpapers/%2e%2e/app.asar"); expect.fail("should have thrown"); } catch (err) { - expect(err).toBeInstanceOf(BackgroundLoadError); - expect((err as BackgroundLoadError).cause).toBeInstanceOf(UnsafeAssetPathError); + if (!(err instanceof BackgroundLoadError)) throw err; + expect(err.cause).toBeInstanceOf(UnsafeAssetPathError); } }); @@ -226,8 +233,8 @@ describe("resolveImageWallpaperUrl", () => { resolveImageWallpaperUrl("/wallpapers/wallpaper1.jpg"); expect.fail("should have thrown"); } catch (err) { - expect(err).toBeInstanceOf(BackgroundLoadError); - expect((err as BackgroundLoadError).cause).toBeInstanceOf(AssetBaseUnavailableError); + if (!(err instanceof BackgroundLoadError)) throw err; + expect(err.cause).toBeInstanceOf(AssetBaseUnavailableError); } }); }); diff --git a/src/lib/wallpaper.ts b/src/lib/wallpaper.ts index 7361df2..6974a04 100644 --- a/src/lib/wallpaper.ts +++ b/src/lib/wallpaper.ts @@ -37,6 +37,13 @@ export function classifyWallpaper(value: string): WallpaperClassification { const ALLOWED_IMAGE_PREFIX = "/wallpapers/"; +export class UnsafeImagePrefixError extends Error { + constructor(prefix: string) { + super(`Image wallpaper path must live under ${prefix}`); + this.name = "UnsafeImagePrefixError"; + } +} + export function resolveImageWallpaperUrl(imagePath: string): string { if ( imagePath.startsWith("http://") || @@ -48,10 +55,7 @@ export function resolveImageWallpaperUrl(imagePath: string): string { } const withLeadingSlash = imagePath.startsWith("/") ? imagePath : `/${imagePath}`; if (!withLeadingSlash.startsWith(ALLOWED_IMAGE_PREFIX)) { - throw new BackgroundLoadError( - imagePath, - new Error(`Image wallpaper path must live under ${ALLOWED_IMAGE_PREFIX}`), - ); + throw new BackgroundLoadError(imagePath, new UnsafeImagePrefixError(ALLOWED_IMAGE_PREFIX)); } try { return getAssetPath(withLeadingSlash.slice(1));