diff --git a/server/constants/media.ts b/server/constants/media.ts index 1b03df6..4eda20e 100644 --- a/server/constants/media.ts +++ b/server/constants/media.ts @@ -2,23 +2,57 @@ function trimSlashes(input: string): string { return input.trim().replace(/^\/+|\/+$/g, ""); } +function hasParentSegment(input: string): boolean { + return input + .split("/") + .map((part) => part.trim()) + .some((part) => part === ".."); +} + +function ensureRelativeDir(input: string, fallback: string, envName: string): string { + const value = input.trim(); + if (!value) { + return fallback; + } + // 仅允许相对目录;绝对路径会绕过项目根约束。 + if (value.startsWith("/")) { + throw new Error(`${envName} must be a relative directory path`); + } + return value; +} + +function ensureSafeSubdir(input: string, fallback: string, envName: string): string { + const value = trimSlashes(input); + if (!value) { + return fallback; + } + if (hasParentSegment(value)) { + throw new Error(`${envName} must not contain ".." segments`); + } + return value; +} + /** 静态资源 URL 前缀固定为 `/static`,不允许通过环境变量覆写。 */ export const STATIC_PUBLIC_PREFIX = "/static"; /** 静态资源根目录,默认 `static` */ -export const STATIC_DIR = trimSlashes(process.env.STATIC_DIR ?? "static"); +export const STATIC_DIR = ensureRelativeDir(process.env.STATIC_DIR ?? "static", "static", "STATIC_DIR"); /** 媒体上传子目录(相对 STATIC_DIR),默认 `media` */ -export const MEDIA_UPLOAD_SUBDIR = trimSlashes(process.env.MEDIA_UPLOAD_SUBDIR ?? "media"); +export const MEDIA_UPLOAD_SUBDIR = ensureSafeSubdir( + process.env.MEDIA_UPLOAD_SUBDIR ?? "media", + "media", + "MEDIA_UPLOAD_SUBDIR", +); /** 媒体上传目录(相对项目根),默认 `static/media` */ export const RELATIVE_ASSETS_DIR = `${STATIC_DIR}/${MEDIA_UPLOAD_SUBDIR}`; /** 临时目录(相对项目根),默认 `.tmp` */ -export const RELATIVE_TMP_DIR = trimSlashes(process.env.TMP_DIR ?? ".tmp"); +export const RELATIVE_TMP_DIR = ensureRelativeDir(process.env.TMP_DIR ?? ".tmp", ".tmp", "TMP_DIR"); /** 与 `media` 返回及静态路径一致,无前导 host */ -export const POST_MEDIA_PUBLIC_PREFIX = `/${RELATIVE_ASSETS_DIR}/`; +export const POST_MEDIA_PUBLIC_PREFIX = `${STATIC_PUBLIC_PREFIX}/${MEDIA_UPLOAD_SUBDIR}/`; /** 从未引用:created_at 起算;曾引用:dereferenced_at 起算 */ export const MEDIA_ORPHAN_GRACE_HOURS_NEVER_REF = 24;