// Package review provides app review and validation services package review import ( "archive/zip" "context" "fmt" "io" "regexp" "strings" "time" "omixlab.com/mosis-portal/internal/database" "omixlab.com/mosis-portal/pkg/mospkg" ) // ReviewFlag represents a security or quality flag type ReviewFlag struct { Type string `json:"type"` Severity string `json:"severity"` // "info", "warning", "critical" Reason string `json:"reason"` File string `json:"file,omitempty"` Line int `json:"line,omitempty"` } // FullValidationResult extends ValidationResult with security flags type FullValidationResult struct { *mospkg.ValidationResult Flags []ReviewFlag `json:"flags,omitempty"` RequiresManual bool `json:"requires_manual"` AutoApprovable bool `json:"auto_approvable"` ValidationTimeMs int64 `json:"validation_time_ms"` } // Service handles app review operations type Service struct { db *database.DB } // New creates a new review service func New(db *database.DB) *Service { return &Service{db: db} } // ValidatePackage performs full validation on a package func (s *Service) ValidatePackage(packagePath string) (*FullValidationResult, error) { start := time.Now() // Run basic validation basicResult, err := mospkg.ValidatePackage(packagePath) if err != nil { return nil, err } result := &FullValidationResult{ ValidationResult: basicResult, Flags: []ReviewFlag{}, AutoApprovable: true, } // If basic validation failed, no need to continue if !basicResult.Valid { result.AutoApprovable = false result.ValidationTimeMs = time.Since(start).Milliseconds() return result, nil } // Run security analysis (Tier 3) flags, err := s.analyzeSecurityRisks(packagePath) if err != nil { return nil, err } result.Flags = append(result.Flags, flags...) // Run quality checks (Tier 4) qualityFlags := s.checkQuality(basicResult.Manifest) result.Flags = append(result.Flags, qualityFlags...) // Determine if manual review is required result.RequiresManual = s.requiresManualReview(result) if result.RequiresManual { result.AutoApprovable = false } // Check for critical flags for _, flag := range result.Flags { if flag.Severity == "critical" { result.AutoApprovable = false } } result.ValidationTimeMs = time.Since(start).Milliseconds() return result, nil } // SubmitForReview submits a version for review func (s *Service) SubmitForReview(ctx context.Context, versionID string) error { return s.db.UpdateVersionStatus(ctx, versionID, "in_review") } // ApproveVersion approves a version func (s *Service) ApproveVersion(ctx context.Context, versionID, reviewerNotes string) error { return s.db.ApproveVersion(ctx, versionID, reviewerNotes) } // RejectVersion rejects a version with feedback func (s *Service) RejectVersion(ctx context.Context, versionID string, feedback *RejectionFeedback) error { return s.db.RejectVersion(ctx, versionID, feedback.Reason, feedback.Message) } // RejectionFeedback contains rejection details type RejectionFeedback struct { Reason string `json:"reason"` Message string `json:"message"` Details []RejectionDetail `json:"details,omitempty"` CanResubmit bool `json:"can_resubmit"` } // RejectionDetail provides specific feedback for an issue type RejectionDetail struct { File string `json:"file"` Line int `json:"line,omitempty"` Issue string `json:"issue"` Suggestion string `json:"suggestion,omitempty"` } // Dangerous patterns to detect in Lua code var dangerousPatterns = []struct { Pattern *regexp.Regexp Reason string Severity string }{ { regexp.MustCompile(`loadstring\s*\(`), "Dynamic code execution via loadstring", "critical", }, { regexp.MustCompile(`load\s*\([^)]*\)`), "Dynamic code loading", "critical", }, { regexp.MustCompile(`debug\s*\.\s*\w+`), "Debug library usage", "critical", }, { regexp.MustCompile(`os\s*\.\s*execute`), "OS command execution", "critical", }, { regexp.MustCompile(`os\s*\.\s*remove`), "File deletion via os.remove", "critical", }, { regexp.MustCompile(`io\s*\.\s*(open|popen|read|write|lines)`), "Direct file I/O operations", "critical", }, { regexp.MustCompile(`ffi\s*\.\s*\w+`), "FFI (foreign function interface) usage", "critical", }, { regexp.MustCompile(`package\s*\.\s*loadlib`), "Native library loading", "critical", }, { regexp.MustCompile(`package\s*\.\s*cpath`), "C library path modification", "critical", }, { regexp.MustCompile(`rawset\s*\(\s*_G`), "Global environment modification", "warning", }, { regexp.MustCompile(`setfenv\s*\(`), "Environment modification", "warning", }, { regexp.MustCompile(`getfenv\s*\(`), "Environment access", "warning", }, { regexp.MustCompile(`https?://[^\s"']+`), "Hardcoded external URL", "info", }, { regexp.MustCompile(`require\s*\(\s*["'][^"']+["']\s*\)`), "Module require (verify allowed modules)", "info", }, } func (s *Service) analyzeSecurityRisks(packagePath string) ([]ReviewFlag, error) { var flags []ReviewFlag reader, err := zip.OpenReader(packagePath) if err != nil { return nil, err } defer reader.Close() for _, file := range reader.File { if file.FileInfo().IsDir() { continue } ext := strings.ToLower(strings.TrimPrefix(file.Name, ".")) if !strings.HasSuffix(file.Name, ".lua") { continue } // Read Lua file content rc, err := file.Open() if err != nil { continue } content, err := io.ReadAll(io.LimitReader(rc, 1024*1024)) // 1MB limit rc.Close() if err != nil { continue } contentStr := string(content) // Check against dangerous patterns for _, dp := range dangerousPatterns { if dp.Pattern.MatchString(contentStr) { // Find line number lineNum := findLineNumber(contentStr, dp.Pattern) flags = append(flags, ReviewFlag{ Type: "SECURITY", Severity: dp.Severity, Reason: dp.Reason, File: file.Name, Line: lineNum, }) } } // Check for obfuscated code (high entropy, meaningless variable names) if isLikelyObfuscated(contentStr) { flags = append(flags, ReviewFlag{ Type: "SECURITY", Severity: "warning", Reason: "Code appears to be obfuscated", File: file.Name, }) } _ = ext // Unused but may be useful for future file type checks } return flags, nil } func findLineNumber(content string, pattern *regexp.Regexp) int { loc := pattern.FindStringIndex(content) if loc == nil { return 0 } lineNum := 1 for i := 0; i < loc[0] && i < len(content); i++ { if content[i] == '\n' { lineNum++ } } return lineNum } func isLikelyObfuscated(content string) bool { // Simple heuristics for obfuscation detection: // 1. High ratio of single-character variable names // 2. Many string.char() calls // 3. Long lines with minimal whitespace singleCharVars := regexp.MustCompile(`\blocal\s+[a-z]\s*=`) matches := singleCharVars.FindAllString(content, -1) if len(matches) > 20 { return true } stringCharCalls := strings.Count(content, "string.char") if stringCharCalls > 10 { return true } // Check for long lines (obfuscators often produce very long lines) lines := strings.Split(content, "\n") longLines := 0 for _, line := range lines { if len(line) > 500 { longLines++ } } if longLines > 3 { return true } return false } func (s *Service) checkQuality(manifest *mospkg.Manifest) []ReviewFlag { var flags []ReviewFlag if manifest == nil { return flags } // Check description length if len(manifest.Description) < 10 { flags = append(flags, ReviewFlag{ Type: "QUALITY", Severity: "info", Reason: "Description is very short (less than 10 characters)", }) } // Check for placeholder icon paths if manifest.Icons.Size32 == "" && manifest.Icons.Size64 == "" && manifest.Icons.Size128 == "" { flags = append(flags, ReviewFlag{ Type: "QUALITY", Severity: "warning", Reason: "No icons specified in manifest", }) } // Check for sensitive permissions sensitivePerms := map[string]bool{ "camera": true, "microphone": true, "contacts": true, "location": true, } for _, perm := range manifest.Permissions { if sensitivePerms[perm] { flags = append(flags, ReviewFlag{ Type: "PERMISSION", Severity: "info", Reason: fmt.Sprintf("App requests sensitive permission: %s", perm), }) } } return flags } func (s *Service) requiresManualReview(result *FullValidationResult) bool { // Always require manual review for: // 1. Any critical security flags // 2. More than 3 warnings // 3. Sensitive permissions criticalCount := 0 warningCount := 0 hasSensitivePerms := false for _, flag := range result.Flags { switch flag.Severity { case "critical": criticalCount++ case "warning": warningCount++ } if flag.Type == "PERMISSION" { hasSensitivePerms = true } } if criticalCount > 0 { return true } if warningCount > 3 { return true } if hasSensitivePerms { return true } return false } // GetReviewQueue returns versions pending review func (s *Service) GetReviewQueue(ctx context.Context, limit, offset int) ([]database.VersionWithApp, int, error) { return s.db.GetVersionsInReview(ctx, limit, offset) } // GetReviewDetails returns details for a specific version under review func (s *Service) GetReviewDetails(ctx context.Context, versionID string) (*database.VersionWithApp, error) { return s.db.GetVersionWithApp(ctx, versionID) }