fix(security): validate exec.Command paths in Go services
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
816676906a
commit
430cc5eef6
5 changed files with 61 additions and 1 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue