Skip to content
Merged
13 changes: 13 additions & 0 deletions backend/src/lib/safePath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ function isFilesystemRoot(rootPath: string): boolean {
return path.parse(rootPath).root === rootPath;
}

/** Resolves an absolute path while rejecting empty, null-byte, relative, and root paths. */
export function resolveAbsoluteNonRootPath(value: string, label: string): string {
const trimmed = value.trim();
if (!trimmed || trimmed.includes("\0") || !path.isAbsolute(trimmed)) {
throw new TypeError(`${label} must be an absolute non-root path`);
}
const resolved = path.resolve(trimmed);
if (isFilesystemRoot(resolved)) {
throw new TypeError(`${label} must be an absolute non-root path`);
}
return resolved;
}

function isWithinCanonicalRoot(candidate: string, root: string, normalizedRoot: string) {
return candidate === root || candidate.startsWith(normalizedRoot);
}
Expand Down
10 changes: 10 additions & 0 deletions backend/src/lib/values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ export function nonEmptyEnvironmentFallback(name: string, fallback: string): str
return value && value.length > 0 ? value : fallback;
}

/** Returns the validated effective Dashboard listen port. */
export function resolveDashboardPort(value = process.env.PORT): number {
const trimmed = value?.trim() ?? "";
if (!/^\d+$/u.test(trimmed)) {
return 3100;
}
const port = Number(trimmed);
return port > 0 && port <= 65_535 ? port : 3100;
}

/** Converts optional values to strings while preserving empty/undefined fallback behavior. */
export function stringFallback(value?: unknown, fallback = ""): string {
return String(value ?? fallback);
Expand Down
Loading