diff --git a/veza-backend-api/internal/services/audio_transcode_service.go b/veza-backend-api/internal/services/audio_transcode_service.go index 547ff1daa..bf92f6ec4 100644 --- a/veza-backend-api/internal/services/audio_transcode_service.go +++ b/veza-backend-api/internal/services/audio_transcode_service.go @@ -9,6 +9,8 @@ import ( "strings" "time" + "veza-backend-api/internal/utils" + "go.uber.org/zap" ) @@ -94,6 +96,13 @@ func (s *AudioTranscodeService) Transcode( inputPath string, options TranscodeOptions, ) (*TranscodeResult, error) { + // SECURITY: Validate paths for exec.Command + if !utils.ValidateExecPath(inputPath) { + return nil, fmt.Errorf("invalid input path") + } + if options.OutputPath != "" && !utils.ValidateExecPath(options.OutputPath) { + return nil, fmt.Errorf("invalid output path") + } // Validate input file if _, err := os.Stat(inputPath); os.IsNotExist(err) { return nil, fmt.Errorf("input file does not exist: %s", inputPath) @@ -120,6 +129,9 @@ func (s *AudioTranscodeService) Transcode( base := strings.TrimSuffix(inputPath, ext) outputPath = fmt.Sprintf("%s_%s.%s", base, string(options.Format), string(options.Format)) } + if !utils.ValidateExecPath(outputPath) { + return nil, fmt.Errorf("invalid output path") + } // Ensure output directory exists if err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil { @@ -292,6 +304,9 @@ func (s *AudioTranscodeService) getBitrateForQuality(quality AudioQuality, forma // getAudioMetadata extracts audio metadata using ffprobe func (s *AudioTranscodeService) getAudioMetadata(ctx context.Context, filePath string) (sampleRate, channels int) { + if !utils.ValidateExecPath(filePath) { + return 44100, 2 // Default on invalid path + } // Try to use ffprobe if available ffprobePath := strings.Replace(s.ffmpegPath, "ffmpeg", "ffprobe", 1) cmd := exec.CommandContext(ctx, ffprobePath, diff --git a/veza-backend-api/internal/services/backup_service.go b/veza-backend-api/internal/services/backup_service.go index b9a1a8957..c942f0e90 100644 --- a/veza-backend-api/internal/services/backup_service.go +++ b/veza-backend-api/internal/services/backup_service.go @@ -8,6 +8,8 @@ import ( "path/filepath" "time" + "veza-backend-api/internal/utils" + "go.uber.org/zap" ) @@ -79,6 +81,14 @@ func (bs *BackupService) CreateBackup(ctx context.Context) (*BackupResult, error backupFileName := fmt.Sprintf("%s_%s.sql", bs.databaseName, timestamp) backupPath := filepath.Join(bs.backupDir, backupFileName) + // SECURITY: Validate path for exec.Command + if !utils.ValidateExecPath(backupPath) { + return &BackupResult{ + Success: false, ErrorMessage: "invalid backup path", + Duration: time.Since(startTime), CreatedAt: time.Now(), + }, fmt.Errorf("invalid backup path") + } + bs.logger.Info("Creating database backup", zap.String("database", bs.databaseName), zap.String("backup_path", backupPath)) diff --git a/veza-backend-api/internal/services/hls_transcode_service.go b/veza-backend-api/internal/services/hls_transcode_service.go index e815f8c0f..2d150cf40 100644 --- a/veza-backend-api/internal/services/hls_transcode_service.go +++ b/veza-backend-api/internal/services/hls_transcode_service.go @@ -9,6 +9,7 @@ import ( "strings" "veza-backend-api/internal/models" + "veza-backend-api/internal/utils" "github.com/google/uuid" @@ -112,6 +113,11 @@ func (s *HLSTranscodeService) transcodeBitrate(ctx context.Context, track *model outputPattern := filepath.Join(qualityDir, "segment_%03d.ts") playlistPath := filepath.Join(qualityDir, "playlist.m3u8") + // SECURITY: Validate paths for exec.Command + if !utils.ValidateExecPath(track.FilePath) || !utils.ValidateExecPath(playlistPath) { + return fmt.Errorf("invalid file path") + } + // Commande ffmpeg pour transcoder en HLS cmd := exec.CommandContext(ctx, "ffmpeg", "-i", track.FilePath, diff --git a/veza-backend-api/internal/services/track_export_service.go b/veza-backend-api/internal/services/track_export_service.go index 590b4e8c2..8d0858f56 100644 --- a/veza-backend-api/internal/services/track_export_service.go +++ b/veza-backend-api/internal/services/track_export_service.go @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" "go.uber.org/zap" "veza-backend-api/internal/models" + "veza-backend-api/internal/utils" ) var ( @@ -61,6 +62,15 @@ func (s *TrackExportService) ExportTrack(ctx context.Context, track *models.Trac return "", ErrExportFormatNotSupported } + // SECURITY: Validate paths for exec.Command + if !utils.ValidateExecPath(track.FilePath) { + return "", fmt.Errorf("%w: invalid file path", ErrExportFailed) + } + exportPath := s.getExportPath(track.ID, format) + if !utils.ValidateExecPath(exportPath) { + return "", fmt.Errorf("%w: invalid export path", ErrExportFailed) + } + // Vérifier que le fichier source existe if _, err := os.Stat(track.FilePath); os.IsNotExist(err) { s.logger.Error("Source file not found", @@ -70,7 +80,6 @@ func (s *TrackExportService) ExportTrack(ctx context.Context, track *models.Trac } // Vérifier si le fichier exporté existe déjà (cache) - exportPath := s.getExportPath(track.ID, format) if _, err := os.Stat(exportPath); err == nil { s.logger.Info("Using cached export", zap.String("track_id", track.ID.String()), diff --git a/veza-backend-api/internal/utils/sanitizer.go b/veza-backend-api/internal/utils/sanitizer.go index bfdaa62f3..c64287608 100644 --- a/veza-backend-api/internal/utils/sanitizer.go +++ b/veza-backend-api/internal/utils/sanitizer.go @@ -2,11 +2,31 @@ package utils import ( "html" + "path/filepath" "regexp" "strings" "unicode" ) +// ValidateExecPath validates a file path for safe use in exec.Command. +// Rejects path traversal (..), null bytes, and shell metacharacters. +func ValidateExecPath(path string) bool { + if path == "" || strings.Contains(path, "\x00") { + return false + } + if strings.Contains(path, "..") { + return false + } + dangerous := []string{"|", "&", ";", "$", "`", "(", ")", "<", ">", "\n", "\r"} + for _, c := range dangerous { + if strings.Contains(path, c) { + return false + } + } + _ = filepath.Clean(path) + return true +} + // BE-SEC-009: Input sanitization to prevent XSS and injection attacks // SanitizeInput sanitizes user input to prevent XSS and injection attacks