fix(security): validate exec.Command paths in Go services

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
senke 2026-02-11 21:32:38 +01:00
parent 816676906a
commit 430cc5eef6
5 changed files with 61 additions and 1 deletions

View file

@ -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,

View file

@ -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))

View file

@ -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,

View file

@ -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()),

View file

@ -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