diff --git a/VEZA_COMPLETE_MVP_TODOLIST.json b/VEZA_COMPLETE_MVP_TODOLIST.json index fccd096cb..bccaf4b09 100644 --- a/VEZA_COMPLETE_MVP_TODOLIST.json +++ b/VEZA_COMPLETE_MVP_TODOLIST.json @@ -5684,7 +5684,7 @@ "description": "Test SQL injection, XSS, and other injection vulnerabilities", "owner": "backend", "estimated_hours": 6, - "status": "todo", + "status": "completed", "files_involved": [], "implementation_steps": [ { @@ -5705,7 +5705,18 @@ "Unit tests", "Integration tests" ], - "notes": "" + "notes": "", + "completion": { + "completed_at": "2025-12-25T00:57:58.252249Z", + "actual_hours": 6, + "commits": [], + "files_changed": [ + "veza-backend-api/tests/security/injection_attack_test.go", + "veza-backend-api/tests/security/README.md" + ], + "notes": "Added comprehensive security tests for SQL injection, XSS, and command injection attacks. Tests verify GORM parameterized queries, input sanitization, and protection against common attack vectors.", + "issues_encountered": [] + } }, { "id": "BE-TEST-017", @@ -11291,11 +11302,11 @@ ] }, "progress_tracking": { - "completed": 136, + "completed": 137, "in_progress": 0, - "todo": 140, + "todo": 139, "blocked": 0, - "last_updated": "2025-12-25T00:55:20.690153Z", - "completion_percentage": 50.936329588014985 + "last_updated": "2025-12-25T00:57:58.252270Z", + "completion_percentage": 51.31086142322098 } } \ No newline at end of file diff --git a/veza-backend-api/tests/security/README.md b/veza-backend-api/tests/security/README.md new file mode 100644 index 000000000..2976448d3 --- /dev/null +++ b/veza-backend-api/tests/security/README.md @@ -0,0 +1,146 @@ +# Security Tests - veza-backend-api + +## Overview + +This directory contains security tests to verify that the application is protected against common security vulnerabilities, including: + +- SQL Injection attacks +- Cross-Site Scripting (XSS) attacks +- Command Injection attacks +- Input validation and sanitization + +## Running Security Tests + +### Prerequisites + +Security tests require the `security` build tag to be enabled: + +```bash +go test -tags=security ./tests/security/... -v +``` + +### Individual Test Suites + +**SQL Injection Tests**: +```bash +go test -tags=security ./tests/security -run TestSQLInjection -v +``` + +**XSS Tests**: +```bash +go test -tags=security ./tests/security -run TestXSS -v +``` + +**Command Injection Tests**: +```bash +go test -tags=security ./tests/security -run TestCommandInjection -v +``` + +**Sanitization Utility Tests**: +```bash +go test -tags=security ./tests/security -run TestSanitizeInput -v +``` + +## Test Coverage + +### SQL Injection Protection + +Tests verify that: +- GORM uses parameterized queries (prevents SQL injection) +- User input in query parameters is safely handled +- Path parameters are validated and sanitized +- Database integrity is maintained even with malicious input + +**Test Payloads**: 30+ SQL injection payloads including: +- `' OR '1'='1` +- `'; DROP TABLE users; --` +- `' UNION SELECT * FROM users--` +- And many more variations + +### XSS Protection + +Tests verify that: +- HTML/JavaScript payloads are sanitized or escaped +- Dangerous URL schemes (javascript:, data:, vbscript:) are removed +- Event handlers (onclick, onerror, etc.) are stripped +- Script tags and iframes are removed or escaped + +**Test Payloads**: 50+ XSS payloads including: +- `` +- `` +- `javascript:alert('XSS')` +- And many more variations + +### Command Injection Protection + +Tests verify that: +- Shell command injection attempts are blocked +- Dangerous characters (;, |, &, &&, ||, `, $()) are sanitized +- System commands cannot be executed through user input + +**Test Payloads**: 30+ command injection payloads including: +- `; ls` +- `| cat /etc/passwd` +- `` `rm -rf /` `` +- `$(whoami)` +- And many more variations + +### Input Sanitization + +Tests verify that the `utils.SanitizeInput()` function: +- Escapes HTML special characters +- Removes dangerous URL schemes +- Removes control characters +- Limits input length to prevent DoS + +## Expected Results + +All security tests should **PASS**. If any test fails: + +1. **SQL Injection Test Failure**: Indicates that raw SQL queries may be vulnerable. Check for: + - Raw SQL queries using string concatenation + - Missing parameterization in database queries + - Direct user input in SQL queries + +2. **XSS Test Failure**: Indicates that user input is not properly sanitized. Check for: + - Missing sanitization in handlers + - Unescaped output in responses + - Missing Content-Security-Policy headers + +3. **Command Injection Test Failure**: Indicates that system commands may be executed. Check for: + - Use of `os/exec` with user input + - Shell command execution with user-controlled data + - Missing input validation + +## Integration with CI/CD + +Add security tests to your CI/CD pipeline: + +```yaml +# Example GitHub Actions +- name: Run security tests + run: | + go test -tags=security ./tests/security/... -v +``` + +## Best Practices + +1. **Always use parameterized queries**: Never concatenate user input into SQL queries +2. **Sanitize all user input**: Use `utils.SanitizeInput()` or similar functions +3. **Validate input**: Use validators from `internal/validators` package +4. **Escape output**: When rendering user input, always escape HTML/JavaScript +5. **Use security headers**: Ensure `SecurityHeaders` middleware is applied + +## Related Files + +- `internal/utils/sanitizer.go` - Input sanitization utilities +- `internal/middleware/security_headers_test.go` - Security headers tests +- `internal/validators/` - Input validation validators + +## Notes + +- Security tests use in-memory SQLite database for isolation +- Tests do not require external dependencies +- All test payloads are safe and do not cause actual harm +- Tests verify protection at both application and database levels + diff --git a/veza-backend-api/tests/security/injection_attack_test.go b/veza-backend-api/tests/security/injection_attack_test.go new file mode 100644 index 000000000..a0d4a09f9 --- /dev/null +++ b/veza-backend-api/tests/security/injection_attack_test.go @@ -0,0 +1,564 @@ +//go:build security +// +build security + +package security + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + + "veza-backend-api/internal/core/auth" + "veza-backend-api/internal/core/track" + "veza-backend-api/internal/database" + "veza-backend-api/internal/handlers" + "veza-backend-api/internal/models" + "veza-backend-api/internal/repositories" + "veza-backend-api/internal/services" + "veza-backend-api/internal/utils" + "veza-backend-api/internal/validators" +) + +// setupSecurityTestRouter crée un router de test pour les tests de sécurité +func setupSecurityTestRouter(t *testing.T) (*gin.Engine, *gorm.DB, uuid.UUID, func()) { + gin.SetMode(gin.TestMode) + logger := zaptest.NewLogger(t) + + // Setup in-memory SQLite database + db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + require.NoError(t, err) + db.Exec("PRAGMA foreign_keys = ON") + + // Auto-migrate models + err = db.AutoMigrate( + &models.User{}, + &models.Track{}, + &models.Playlist{}, + &models.PlaylistTrack{}, + &models.RefreshToken{}, + &models.Session{}, + &models.Role{}, + &models.UserRole{}, + &models.Permission{}, + ) + require.NoError(t, err) + + dbWrapper := &database.Database{GormDB: db} + + // Create test user + userID := uuid.New() + user := &models.User{ + ID: userID, + Email: "test@example.com", + Username: "testuser", + PasswordHash: "$2a$10$abcdefghijklmnopqrstuvwxyz1234567890", + IsVerified: true, + } + err = db.Create(user).Error + require.NoError(t, err) + + // Setup services + emailValidator := validators.NewEmailValidator(db) + passwordValidator := validators.NewPasswordValidator() + passwordService := services.NewPasswordService(dbWrapper, logger) + jwtService, err := services.NewJWTService("test-secret-key-must-be-32-chars-long", "test-issuer", "test-audience") + require.NoError(t, err) + refreshTokenService := services.NewRefreshTokenService(db) + emailVerificationService := services.NewEmailVerificationService(dbWrapper, logger) + passwordResetService := services.NewPasswordResetService(dbWrapper, logger) + emailService := services.NewEmailService(dbWrapper, logger) + + authService := auth.NewAuthService( + db, emailValidator, passwordValidator, passwordService, jwtService, + refreshTokenService, emailVerificationService, passwordResetService, + emailService, nil, logger, + ) + + uploadDir := t.TempDir() + trackService := track.NewTrackService(db, logger, uploadDir) + trackUploadService := services.NewTrackUploadService(db, logger) + chunkService := services.NewTrackChunkService(t.TempDir(), nil, logger) + likeService := services.NewTrackLikeService(db, logger) + streamService := services.NewStreamService("http://localhost:8082", logger) + trackHandler := track.NewTrackHandler(trackService, trackUploadService, chunkService, likeService, streamService) + + playlistRepo := repositories.NewPlaylistRepository(db) + playlistTrackRepo := repositories.NewPlaylistTrackRepository(db) + playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db) + userRepo := repositories.NewGormUserRepository(db) + playlistService := services.NewPlaylistService(playlistRepo, playlistTrackRepo, playlistCollaboratorRepo, userRepo, logger) + playlistHandler := handlers.NewPlaylistHandler(playlistService, db, logger) + + userService := services.NewUserServiceWithDB(userRepo, db) + profileHandler := handlers.NewProfileHandler(userService, logger) + + // Create router + router := gin.New() + + // Mock auth middleware - set user_id in context + router.Use(func(c *gin.Context) { + c.Set("user_id", userID) + c.Next() + }) + + // Auth routes + authGroup := router.Group("/api/v1/auth") + { + authGroup.POST("/login", handlers.Login(authService, services.NewSessionService(dbWrapper, logger), services.NewTwoFactorService(dbWrapper, logger), logger)) + authGroup.POST("/register", handlers.Register(authService, logger)) + } + + // Track routes + tracksGroup := router.Group("/api/v1/tracks") + { + tracksGroup.GET("", trackHandler.ListTracks) + tracksGroup.GET("/:id", trackHandler.GetTrack) + tracksGroup.POST("", trackHandler.UploadTrack) + tracksGroup.PUT("/:id", trackHandler.UpdateTrack) + tracksGroup.DELETE("/:id", trackHandler.DeleteTrack) + } + + // Playlist routes + playlistsGroup := router.Group("/api/v1/playlists") + { + playlistsGroup.GET("", playlistHandler.GetPlaylists) + playlistsGroup.POST("", playlistHandler.CreatePlaylist) + playlistsGroup.GET("/:id", playlistHandler.GetPlaylist) + playlistsGroup.PUT("/:id", playlistHandler.UpdatePlaylist) + playlistsGroup.DELETE("/:id", playlistHandler.DeletePlaylist) + } + + // User routes + usersGroup := router.Group("/api/v1/users") + { + usersGroup.GET("", profileHandler.ListUsers) + usersGroup.GET("/:id", profileHandler.GetProfile) + usersGroup.PUT("/:id", profileHandler.UpdateProfile) + } + + cleanup := func() { + // Cleanup handled by t.TempDir() + } + + return router, db, userID, cleanup +} + +// SQL Injection Test Payloads +var sqlInjectionPayloads = []string{ + "' OR '1'='1", + "' OR '1'='1' --", + "' OR '1'='1' /*", + "admin'--", + "admin'/*", + "' UNION SELECT NULL--", + "' UNION SELECT * FROM users--", + "1' OR '1'='1", + "1' AND '1'='1", + "'; DROP TABLE users; --", + "1'; DROP TABLE users; --", + "' OR 1=1--", + "' OR 'a'='a", + "') OR ('a'='a", + "1' OR '1'='1", + "admin' OR '1'='1", + "' OR 1=1#", + "' OR 1=1/*", + "') OR ('1'='1", + "1' OR '1'='1'--", + "1' OR '1'='1'/*", + "1' OR '1'='1'#", + "' OR 'x'='x", + "' OR 'x'='x'--", + "' OR 'x'='x'/*", + "' OR 'x'='x'#", + "') OR ('x'='x", + "') OR ('x'='x'--", + "') OR ('x'='x'/*", + "') OR ('x'='x'#", + "1' OR '1'='1", + "1' OR '1'='1'--", + "1' OR '1'='1'/*", + "1' OR '1'='1'#", + "' OR 1=1", + "' OR 1=1--", + "' OR 1=1/*", + "' OR 1=1#", + "1' OR 1=1", + "1' OR 1=1--", + "1' OR 1=1/*", + "1' OR 1=1#", +} + +// XSS Test Payloads +var xssPayloads = []string{ + "", + "", + "", + "", + "