/** * S3 Key Validation Utility * Validates that user-supplied S3 keys match expected formats * to prevent IDOR and arbitrary data injection attacks */ // UUID v4 regex pattern const UUID_PATTERN = "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"; // Allowed image extensions const ALLOWED_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "webp"]; // Valid S3 folders const VALID_FOLDERS = [ "profiles", "items", "messages", "forum", "condition-checks", ]; /** * Build regex pattern for a specific folder * @param {string} folder - The S3 folder name * @returns {RegExp} */ function buildKeyPattern(folder) { const extPattern = ALLOWED_EXTENSIONS.join("|"); return new RegExp(`^${folder}/${UUID_PATTERN}\\.(${extPattern})$`, "i"); } /** * Validate a single S3 key * @param {string} key - The S3 key to validate * @param {string} folder - Expected folder (profiles, items, messages, forum, condition-checks) * @returns {{ valid: boolean, error?: string }} */ function validateS3Key(key, folder) { if (!key || typeof key !== "string") { return { valid: false, error: "Key must be a non-empty string" }; } if (!VALID_FOLDERS.includes(folder)) { return { valid: false, error: `Invalid folder` }; } const pattern = buildKeyPattern(folder); if (!pattern.test(key)) { return { valid: false, error: `Invalid key format`, }; } return { valid: true }; } /** * Validate an array of S3 keys * @param {Array} keys - Array of S3 keys to validate * @param {string} folder - Expected folder (profiles, items, messages, forum, condition-checks) * @param {Object} options - Validation options * @param {number} options.maxKeys - Maximum number of keys allowed * @returns {{ valid: boolean, error?: string, invalidKeys?: Array }} */ function validateS3Keys(keys, folder, options = {}) { const { maxKeys = 20 } = options; if (!Array.isArray(keys)) { return { valid: false, error: "Keys must be an array" }; } if (keys.length > maxKeys) { return { valid: false, error: `Maximum ${maxKeys} keys allowed` }; } if (keys.length === 0) { return { valid: true }; } const uniqueKeys = new Set(keys); if (uniqueKeys.size !== keys.length) { return { valid: false, error: "Duplicate keys not allowed" }; } const invalidKeys = []; for (const key of keys) { const result = validateS3Key(key, folder); if (!result.valid) { invalidKeys.push({ key, error: result.error }); } } if (invalidKeys.length > 0) { return { valid: false, error: "Invalid S3 key format", invalidKeys }; } return { valid: true }; } module.exports = { validateS3Keys };