61 lines
1.9 KiB
TypeScript
61 lines
1.9 KiB
TypeScript
/**
|
|
* Input sanitization utilities.
|
|
* Prevents XSS, command injection, and path traversal attacks.
|
|
*/
|
|
|
|
/** Shell metacharacters that must not appear in RCON commands sent to the OS. */
|
|
const SHELL_UNSAFE = /[;&|`$(){}[\]<>\\'"*?!#~]/;
|
|
|
|
/**
|
|
* Validate a Minecraft RCON command.
|
|
* Commands go to the MC RCON protocol (not a shell), but we still strip
|
|
* shell metacharacters as a defense-in-depth measure.
|
|
*/
|
|
export function sanitizeRconCommand(cmd: string): string {
|
|
if (typeof cmd !== "string") throw new Error("Command must be a string");
|
|
const trimmed = cmd.trim();
|
|
if (trimmed.length === 0) throw new Error("Command cannot be empty");
|
|
if (trimmed.length > 32767) throw new Error("Command too long");
|
|
// RCON commands start with / or a bare word — never allow OS-level metacharacters
|
|
if (SHELL_UNSAFE.test(trimmed)) {
|
|
throw new Error("Command contains forbidden characters");
|
|
}
|
|
return trimmed;
|
|
}
|
|
|
|
/**
|
|
* Validate and normalize a file system path relative to a base directory.
|
|
* Prevents path traversal (e.g. "../../etc/passwd").
|
|
*/
|
|
export function sanitizeFilePath(
|
|
inputPath: string,
|
|
baseDir: string,
|
|
): string {
|
|
const path = require("node:path");
|
|
const resolved = path.resolve(baseDir, inputPath);
|
|
if (!resolved.startsWith(path.resolve(baseDir))) {
|
|
throw new Error("Path traversal detected");
|
|
}
|
|
return resolved;
|
|
}
|
|
|
|
/**
|
|
* Strip HTML tags from user-provided strings to prevent stored XSS.
|
|
* Use this before storing free-text fields in the database.
|
|
*/
|
|
export function stripHtml(input: string): string {
|
|
return input.replace(/<[^>]*>/g, "");
|
|
}
|
|
|
|
/** Validate a Minecraft UUID (8-4-4-4-12 hex). */
|
|
export function isValidMcUuid(uuid: string): boolean {
|
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
|
|
uuid,
|
|
);
|
|
}
|
|
|
|
/** Validate a Minecraft username (3-16 chars, alphanumeric + underscore). */
|
|
export function isValidMcUsername(name: string): boolean {
|
|
return /^[a-zA-Z0-9_]{3,16}$/.test(name);
|
|
}
|