All files / src/utils pathValidator.js

0% Statements 0/26
0% Branches 0/22
0% Functions 0/2
0% Lines 0/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92                                                                                                                                                                                       
/**
 * Validation des chemins de fichiers — Protection contre le path traversal.
 *
 * Vérifie qu'un chemin fourni par l'utilisateur (ou Claude) est sûr
 * avant de l'utiliser dans une commande SSH.
 *
 * @module utils/pathValidator
 */
 
const path = require('node:path');
 
/**
 * Caractères et séquences interdits dans un chemin de fichier.
 * Ces patterns pourraient être exploités pour de l'injection de commande
 * ou du path traversal si insérés dans une commande shell.
 */
const FORBIDDEN_PATTERNS = [
  /\0/,           // Null byte — troncature de chaîne dans certains systèmes
  /\.\.\//,       // Path traversal avec ../
  /\.\.\\/,       // Path traversal Windows-style
  /^~\//,         // Expansion tilde (pourrait résoudre vers /root)
  /\$\(/,         // Command substitution $(...)
  /`/,            // Backtick command substitution
  /\$\{/,         // Variable expansion ${...}
  /\n/,           // Newline — injection de commande
  /\r/,           // Carriage return
];
 
/**
 * Valide qu'un chemin de fichier est sûr pour une utilisation SSH.
 *
 * @param {string} filePath — Le chemin à valider
 * @returns {{ valid: boolean, reason?: string }}
 */
function validatePath(filePath) {
  if (!filePath || typeof filePath !== 'string') {
    return { valid: false, reason: 'Chemin vide ou invalide.' };
  }
 
  // Le chemin doit être absolu
  if (!filePath.startsWith('/')) {
    return { valid: false, reason: 'Le chemin doit être absolu (commencer par /).' };
  }
 
  // Longueur maximale (Linux PATH_MAX = 4096)
  if (filePath.length > 4096) {
    return { valid: false, reason: 'Chemin trop long (max 4096 caractères).' };
  }
 
  // Vérifier les patterns interdits
  for (const pattern of FORBIDDEN_PATTERNS) {
    if (pattern.test(filePath)) {
      return { valid: false, reason: `Chemin contient un pattern interdit : ${pattern.source}` };
    }
  }
 
  // Normaliser et vérifier que le chemin ne sort pas de la racine
  const normalized = path.posix.normalize(filePath);
  if (normalized.includes('..')) {
    return { valid: false, reason: 'Path traversal détecté après normalisation.' };
  }
 
  // Chemins sensibles interdits en écriture
  const SENSITIVE_PATHS = ['/etc/shadow', '/etc/passwd', '/etc/sudoers'];
  if (SENSITIVE_PATHS.includes(normalized)) {
    return { valid: false, reason: 'Chemin système sensible — accès interdit.' };
  }
 
  return { valid: true };
}
 
/**
 * Valide un nom de container Docker (alphanumérique + tirets + underscores + points).
 *
 * @param {string} name — Le nom ou ID du container
 * @returns {{ valid: boolean, reason?: string }}
 */
function validateContainerName(name) {
  if (!name || typeof name !== 'string') {
    return { valid: false, reason: 'Nom de container vide.' };
  }
  if (name.length > 128) {
    return { valid: false, reason: 'Nom de container trop long.' };
  }
  if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(name)) {
    return { valid: false, reason: 'Nom de container invalide — caractères autorisés : a-z, 0-9, _, -, .' };
  }
  return { valid: true };
}
 
module.exports = { validatePath, validateContainerName };