diff --git a/.gitignore b/.gitignore
index fda9ea0..1c180c6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,6 +52,11 @@ bin/
tmp/
**/*.log.dccache
+# Vim swap files
+*.swp
+*.swo
+*~
+
# Development cache and temporary files
.dccache
*.log
@@ -60,3 +65,4 @@ tmp/
*_improvements.md
*_notes.md
*_todo.md
+TODO.md
diff --git a/.swo b/.swo
deleted file mode 100644
index aa5028a..0000000
Binary files a/.swo and /dev/null differ
diff --git a/CLAUDE.md b/CLAUDE.md
index 1680c74..b5033bd 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -162,13 +162,17 @@ For detailed technical architecture, design patterns, and implementation guideli
**Cross-Package Manager Compatibility**: SysPkg normalizes package states for consistent behavior across different package managers. For example, APT's "config-files" state (packages removed but with configuration files remaining) is normalized to "available" status to match the semantics used by other package managers like YUM and Snap.
+## Current Session Todos
+
+@TODO.md
+
## Project Improvement Roadmap
*Note: To-do list consolidated 2025-05-30 - removed duplicates, feature creep items, and over-engineering. Focused on core security, testing, and platform support.*
### 🔴 High Priority (Security & Critical Bugs) - 15 items
-1. **Fix command injection vulnerability** - validate/sanitize package names before exec.Command
-2. **Implement input validation helper function** for package names and arguments
+1. **Fix command injection vulnerability** ✅ - validate/sanitize package names before exec.Command (PR #25)
+2. **Implement input validation helper function** ✅ for package names and arguments (PR #25)
3. **Fix resource leaks** in error handling paths
4. **Add security scanning with Snyk** to CI/CD pipeline
5. **Review and merge PR #12** - fix GetPackageManager("") panic bug ✅
@@ -179,64 +183,42 @@ For detailed technical architecture, design patterns, and implementation guideli
10. **Implement CommandBuilder interface (Issue #20)** - Replace direct exec.Command calls with testable CommandBuilder pattern
11. **Add exit code documentation** ✅ - Created comprehensive exit code docs for all package managers
-### ✅ CRITICAL INVESTIGATION COMPLETED
-**Investigation Results: No design flaw found - tests are correct**
-8. **RESOLVED: Tests are correctly testing parser functions** ✅ - `behavior_test.go` tests `ParseFindOutput()` directly, not `Find()` method
-9. **RESOLVED: ParseFindOutput() vs Find() distinction clarified** ✅:
- - `ParseFindOutput()`: Pure parser, returns all packages as "available" (YUM limitation)
- - `Find()`: Enhanced method that adds rpm -q status detection for accurate results
-10. **RESOLVED: CommandRunner interface verified** ✅ - Correctly implemented in `enhancePackagesWithStatus()`
-11. **RESOLVED: Test execution paths confirmed** ✅ - Tests correctly test parsers, not enhanced methods
-12. **RESOLVED: Fixtures validated as authentic** ✅ - Real YUM output that correctly lacks status info
-13. **RESOLVED: Interface implementation verified** ✅ - All methods properly implemented and registered
-14. **RESOLVED: Created integration tests** ✅ - Added `yum_integration_test.go` demonstrating three-layer testing approach
-
-### 🟡 Medium Priority (Code Quality & Testing) - 8 items
-**Testing:**
-- **Create integration tests** ✅ - Added `yum_integration_test.go` with three-layer testing approach
-- **Document testing strategy** ✅ - Added comprehensive testing documentation to CONTRIBUTING.md
-- **Implement CommandRunner dependency injection (Issue #20)** 🚧 - YUM partially implemented, architecture decision updated to Option C (CommandBuilder)
-- Add unit tests for snap package manager
-- Add unit tests for flatpak package manager
-- **APT fixture cleanup and behavior testing** ✅ - Reduced 16→7 fixtures, full test coverage
-- **Cross-platform parsing robustness** ✅ - CRLF/whitespace handling, regex optimization
-- **YUM fixture analysis and cleanup** ✅ **COMPLETED** (Issue #16) - Following APT pattern:
- - ✅ Analyzed YUM fixtures to determine what's needed for comprehensive testing
- - ✅ Removed redundant/duplicate files (search-vim-rockylinux.txt)
- - ✅ Standardized fixture naming convention (rocky8 vs rockylinux inconsistency)
- - ✅ Renamed info-vim-rockylinux.txt → info-vim-installed-rocky8.txt for clarity
- - ✅ Added missing edge case fixtures (empty results, not found, clean, refresh)
- - ✅ Created comprehensive behavior_test.go following APT fixture pattern
- - ✅ Converted YUM tests from inline data to fixture-based tests
- - ✅ Verified fixture compatibility and completeness with all tests passing
-- **YUM operations implementation** ✅ **COMPLETED** - Comprehensive YUM package manager:
- - ✅ Implemented all missing operations: Install, Delete, ListUpgradable, Upgrade, UpgradeAll, AutoRemove
- - ✅ Added complete parser functions for all operation outputs
- - ✅ Created comprehensive behavior tests covering all operations and edge cases
- - ✅ Generated real fixtures using Rocky Linux Docker for authentic test data
- - ✅ Documented YUM-specific behaviors and cross-package manager compatibility
- - ✅ **YUM Find() status detection** - Implemented rpm -q integration for accurate installation status
- - ✅ **Cross-package manager API consistency** - YUM Find() now matches APT behavior exactly
- - ✅ All tests passing with 100% security scan clearance
-
-**Documentation:**
-- **API and behavior documentation** ✅ - Enhanced interface docs, status normalization, cross-PM compatibility
-- **Error handling best practices** ✅ - Fixed ignored errors in documentation examples
-- **Accuracy improvements** ✅ - Fixed misleading comments about status handling
-- **YUM documentation updates** ✅ - Updated all outdated behavior comments to reflect Find() status detection capabilities
-
-**Code Improvements:**
-- Implement context support for cancellation and timeouts
-- Create custom error types for better error handling
-- Extract common parsing logic to shared utilities (DRY principle)
-- Replace magic strings/numbers with named constants
-- **Fix APT multi-arch package parsing** (Issue #15) - cosmetic fix for empty package names
+### ✅ COMPLETED INVESTIGATIONS (Collapsed)
+
+Critical investigation results - tests are correctly implemented
-**Removed from roadmap (2025-05-30):**
-- ~~Structured logging~~ (over-engineering for project scope)
-- ~~Progress indicators~~ (feature creep for CLI/library)
-- ~~Architecture diagrams~~ (low ROI for library project)
-- ~~TODO comment fixes~~ (covered by security improvements)
+**Investigation Results: No design flaw found - tests are correct**
+- ✅ **Parser vs enhanced method distinction clarified**
+- ✅ **CommandRunner interface verified**
+- ✅ **Test execution paths confirmed**
+- ✅ **Fixtures validated as authentic**
+- ✅ **Integration tests created**
+
+
+### 🟡 Medium Priority & Completed Items (Collapsed)
+
+Code quality, testing achievements, and removed items
+
+**Completed Testing Work:**
+- ✅ YUM comprehensive implementation (Issue #16)
+- ✅ APT fixture cleanup and behavior testing
+- ✅ Integration tests and testing strategy documentation
+- ✅ Cross-platform parsing robustness
+
+**Completed Documentation:**
+- ✅ API and behavior documentation enhanced
+- ✅ Error handling best practices documented
+- ✅ YUM documentation updates
+
+**Remaining Code Improvements:**
+- Context support for cancellation and timeouts
+- Custom error types for better error handling
+- Extract common parsing logic (DRY principle)
+- Replace magic strings/numbers with constants
+
+**Items Removed from Roadmap (2025-05-30):**
+- ~~Structured logging, progress indicators, architecture diagrams~~ (over-engineering/feature creep)
+
### 🟢 Low Priority (Platform Support) - 3 items
**New Package Managers:**
diff --git a/manager/apt/apt.go b/manager/apt/apt.go
index 534e81d..a336f45 100644
--- a/manager/apt/apt.go
+++ b/manager/apt/apt.go
@@ -86,6 +86,11 @@ func (a *PackageManager) GetPackageManager() string {
// Install installs the provided packages using the apt package manager.
func (a *PackageManager) Install(pkgs []string, opts *manager.Options) ([]manager.PackageInfo, error) {
+ // Validate package names to prevent command injection
+ if err := manager.ValidatePackageNames(pkgs); err != nil {
+ return nil, err
+ }
+
args := append([]string{"install", ArgsFixBroken}, pkgs...)
if opts == nil {
@@ -125,6 +130,11 @@ func (a *PackageManager) Install(pkgs []string, opts *manager.Options) ([]manage
// Delete removes the provided packages using the apt package manager.
func (a *PackageManager) Delete(pkgs []string, opts *manager.Options) ([]manager.PackageInfo, error) {
+ // Validate package names to prevent command injection
+ if err := manager.ValidatePackageNames(pkgs); err != nil {
+ return nil, err
+ }
+
// args := append([]string{"remove", ArgsFixBroken, ArgsPurge, ArgsAutoRemove}, pkgs...)
args := append([]string{"remove", ArgsFixBroken, ArgsAutoRemove}, pkgs...)
if opts == nil {
@@ -192,6 +202,11 @@ func (a *PackageManager) Refresh(opts *manager.Options) error {
// Find searches for packages matching the provided keywords using the apt package manager.
func (a *PackageManager) Find(keywords []string, opts *manager.Options) ([]manager.PackageInfo, error) {
+ // Validate keywords to prevent command injection
+ if err := manager.ValidatePackageNames(keywords); err != nil {
+ return nil, err
+ }
+
args := append([]string{"search"}, keywords...)
cmd := exec.Command("apt", args...)
cmd.Env = append(os.Environ(), ENV_NonInteractive...)
@@ -229,6 +244,13 @@ func (a *PackageManager) ListUpgradable(opts *manager.Options) ([]manager.Packag
// Upgrade upgrades the provided packages using the apt package manager.
func (a *PackageManager) Upgrade(pkgs []string, opts *manager.Options) ([]manager.PackageInfo, error) {
+ // Validate package names to prevent command injection
+ if len(pkgs) > 0 {
+ if err := manager.ValidatePackageNames(pkgs); err != nil {
+ return nil, err
+ }
+ }
+
args := []string{"upgrade"}
if len(pkgs) > 0 {
args = append(args, pkgs...)
@@ -307,6 +329,11 @@ func (a *PackageManager) Clean(opts *manager.Options) error {
// GetPackageInfo retrieves package information for the specified package using the apt package manager.
func (a *PackageManager) GetPackageInfo(pkg string, opts *manager.Options) (manager.PackageInfo, error) {
+ // Validate package name to prevent command injection
+ if err := manager.ValidatePackageName(pkg); err != nil {
+ return manager.PackageInfo{}, err
+ }
+
cmd := exec.Command("apt-cache", "show", pkg)
cmd.Env = ENV_NonInteractive
out, err := cmd.Output()
diff --git a/manager/apt/utils.go b/manager/apt/utils.go
index e0adb71..3c3b8f5 100644
--- a/manager/apt/utils.go
+++ b/manager/apt/utils.go
@@ -311,6 +311,11 @@ func logDebugPackages(packages map[string]manager.PackageInfo, opts *manager.Opt
// runDpkgQuery executes dpkg-query command and handles errors appropriately
func runDpkgQuery(packageNames []string, opts *manager.Options) ([]byte, error) {
+ // Validate package names to prevent command injection
+ if err := manager.ValidatePackageNames(packageNames); err != nil {
+ return nil, err
+ }
+
args := []string{"-W", "--showformat", "${binary:Package} ${Status} ${Version}\n"}
args = append(args, packageNames...)
cmd := exec.Command("dpkg-query", args...)
diff --git a/manager/flatpak/flatpak.go b/manager/flatpak/flatpak.go
index 2fc54c4..f3aefe4 100644
--- a/manager/flatpak/flatpak.go
+++ b/manager/flatpak/flatpak.go
@@ -61,6 +61,11 @@ func (a *PackageManager) GetPackageManager() string {
// Install installs the given packages using Flatpak with the provided options.
func (a *PackageManager) Install(pkgs []string, opts *manager.Options) ([]manager.PackageInfo, error) {
+ // Validate package names to prevent command injection
+ if err := manager.ValidatePackageNames(pkgs); err != nil {
+ return nil, err
+ }
+
args := append([]string{"install", ArgsFixBroken, ArgsUpsert, ArgsVerbose}, pkgs...)
if opts == nil {
@@ -104,6 +109,11 @@ func (a *PackageManager) Install(pkgs []string, opts *manager.Options) ([]manage
// Delete removes the given packages using Flatpak with the provided options.
func (a *PackageManager) Delete(pkgs []string, opts *manager.Options) ([]manager.PackageInfo, error) {
+ // Validate package names to prevent command injection
+ if err := manager.ValidatePackageNames(pkgs); err != nil {
+ return nil, err
+ }
+
args := append([]string{"uninstall", ArgsFixBroken, ArgsVerbose}, pkgs...)
if opts == nil {
@@ -154,6 +164,11 @@ func (a *PackageManager) Refresh(opts *manager.Options) error {
// Find searches for packages matching the given keywords using Flatpak with the provided options.
func (a *PackageManager) Find(keywords []string, opts *manager.Options) ([]manager.PackageInfo, error) {
+ // Validate keywords to prevent command injection
+ if err := manager.ValidatePackageNames(keywords); err != nil {
+ return nil, err
+ }
+
args := append([]string{"search", ArgsVerbose}, keywords...)
if opts == nil {
@@ -248,6 +263,11 @@ func (a *PackageManager) UpgradeAll(opts *manager.Options) ([]manager.PackageInf
// GetPackageInfo retrieves package information for a single package using Flatpak with the provided options.
func (a *PackageManager) GetPackageInfo(pkg string, opts *manager.Options) (manager.PackageInfo, error) {
+ // Validate package name to prevent command injection
+ if err := manager.ValidatePackageName(pkg); err != nil {
+ return manager.PackageInfo{}, err
+ }
+
cmd := exec.Command(pm, "info", pkg)
cmd.Env = ENV_NonInteractive
out, err := cmd.Output()
diff --git a/manager/security.go b/manager/security.go
new file mode 100644
index 0000000..78d45a0
--- /dev/null
+++ b/manager/security.go
@@ -0,0 +1,83 @@
+// Package manager provides security utilities for package manager operations
+package manager
+
+import (
+ "errors"
+ "regexp"
+)
+
+// packageNameRegex defines the allowed pattern for package names
+// This pattern allows:
+// - Letters (a-z, A-Z)
+// - Numbers (0-9)
+// - Dash/hyphen (-)
+// - Underscore (_)
+// - Period/dot (.)
+// - Plus sign (+)
+// - Colon (:) for architecture specifiers (e.g., package:amd64)
+// - Forward slash (/) for repository specifiers (e.g., repo/package)
+// The pattern requires at least one valid character
+var packageNameRegex = regexp.MustCompile(`^[a-zA-Z0-9\-_.+:/]+$`)
+
+// ErrInvalidPackageName is returned when a package name contains invalid characters
+var ErrInvalidPackageName = errors.New("invalid package name: contains potentially dangerous characters")
+
+// ValidatePackageName validates that a package name only contains safe characters
+// to prevent command injection attacks.
+//
+// Valid package names may contain:
+// - Alphanumeric characters (a-z, A-Z, 0-9)
+// - Dash/hyphen (-)
+// - Underscore (_)
+// - Period/dot (.)
+// - Plus sign (+)
+// - Colon (:) for architecture specifiers
+// - Forward slash (/) for repository specifiers
+//
+// This function rejects any package names containing:
+// - Shell metacharacters (;, |, &, $, `, \, ", ', <, >, (, ), {, }, [, ], *, ?, ~)
+// - Whitespace characters
+// - Control characters
+// - Null bytes
+//
+// Example valid names:
+// - "vim"
+// - "libssl1.1"
+// - "gcc-9-base"
+// - "python3.8"
+// - "package:amd64"
+// - "repo/package"
+//
+// Example invalid names:
+// - "package; rm -rf /"
+// - "package && malicious-command"
+// - "package`evil`"
+// - "package$(bad)"
+func ValidatePackageName(name string) error {
+ // Check for empty string
+ if name == "" {
+ return errors.New("package name cannot be empty")
+ }
+
+ // Check length limit (most package managers have reasonable limits)
+ if len(name) > 255 {
+ return errors.New("package name too long (max 255 characters)")
+ }
+
+ // Validate against regex pattern
+ if !packageNameRegex.MatchString(name) {
+ return ErrInvalidPackageName
+ }
+
+ return nil
+}
+
+// ValidatePackageNames validates multiple package names
+func ValidatePackageNames(names []string) error {
+ for _, name := range names {
+ if err := ValidatePackageName(name); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/manager/security_test.go b/manager/security_test.go
new file mode 100644
index 0000000..d9d7007
--- /dev/null
+++ b/manager/security_test.go
@@ -0,0 +1,240 @@
+package manager
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestValidatePackageName(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ wantErr bool
+ errMsg string
+ }{
+ // Valid package names
+ {
+ name: "simple package name",
+ input: "vim",
+ wantErr: false,
+ },
+ {
+ name: "package with version",
+ input: "python3.8",
+ wantErr: false,
+ },
+ {
+ name: "package with dash",
+ input: "gcc-9-base",
+ wantErr: false,
+ },
+ {
+ name: "package with underscore",
+ input: "libc6_dev",
+ wantErr: false,
+ },
+ {
+ name: "package with plus",
+ input: "g++",
+ wantErr: false,
+ },
+ {
+ name: "package with architecture",
+ input: "libc6:amd64",
+ wantErr: false,
+ },
+ {
+ name: "package with repo",
+ input: "ppa/package-name",
+ wantErr: false,
+ },
+ {
+ name: "complex valid name",
+ input: "lib32stdc++-9-dev:i386",
+ wantErr: false,
+ },
+
+ // Invalid package names - command injection attempts
+ {
+ name: "semicolon injection",
+ input: "package; rm -rf /",
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "pipe injection",
+ input: "package | cat /etc/passwd",
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "ampersand injection",
+ input: "package && malicious-command",
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "backtick injection",
+ input: "package`evil`",
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "dollar sign injection",
+ input: "package$(bad)",
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "redirect injection",
+ input: "package > /etc/passwd",
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "single quote injection",
+ input: "package'; drop table users; --",
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "double quote injection",
+ input: `package"; rm -rf /; "`,
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "backslash injection",
+ input: `package\nmalicious`,
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "space injection",
+ input: "package name with spaces",
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "tab injection",
+ input: "package\tmalicious",
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "newline injection",
+ input: "package\nmalicious",
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "null byte injection",
+ input: "package\x00malicious",
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "parenthesis injection",
+ input: "package(malicious)",
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "bracket injection",
+ input: "package[malicious]",
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "curly brace injection",
+ input: "package{malicious}",
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "asterisk injection",
+ input: "package*",
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "question mark injection",
+ input: "package?",
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+ {
+ name: "tilde injection",
+ input: "~package",
+ wantErr: true,
+ errMsg: "invalid package name",
+ },
+
+ // Edge cases
+ {
+ name: "empty string",
+ input: "",
+ wantErr: true,
+ errMsg: "empty",
+ },
+ {
+ name: "very long name",
+ input: string(make([]byte, 256)),
+ wantErr: true,
+ errMsg: "too long",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := ValidatePackageName(tt.input)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("ValidatePackageName(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
+ return
+ }
+ if err != nil && tt.errMsg != "" {
+ if !strings.Contains(err.Error(), tt.errMsg) {
+ t.Errorf("ValidatePackageName(%q) error = %v, want error containing %q", tt.input, err, tt.errMsg)
+ }
+ }
+ })
+ }
+}
+
+func TestValidatePackageNames(t *testing.T) {
+ tests := []struct {
+ name string
+ input []string
+ wantErr bool
+ }{
+ {
+ name: "all valid names",
+ input: []string{"vim", "git", "python3.8"},
+ wantErr: false,
+ },
+ {
+ name: "one invalid name",
+ input: []string{"vim", "git; rm -rf /", "python3.8"},
+ wantErr: true,
+ },
+ {
+ name: "empty slice",
+ input: []string{},
+ wantErr: false,
+ },
+ {
+ name: "empty string in slice",
+ input: []string{"vim", "", "git"},
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := ValidatePackageNames(tt.input)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("ValidatePackageNames(%v) error = %v, wantErr %v", tt.input, err, tt.wantErr)
+ }
+ })
+ }
+}
diff --git a/manager/snap/snap.go b/manager/snap/snap.go
index 43ceff6..c21c122 100644
--- a/manager/snap/snap.go
+++ b/manager/snap/snap.go
@@ -56,6 +56,11 @@ func (a *PackageManager) GetPackageManager() string {
// Install installs the specified packages using the snap package manager with the provided options.
func (a *PackageManager) Install(pkgs []string, opts *manager.Options) ([]manager.PackageInfo, error) {
+ // Validate package names to prevent command injection
+ if err := manager.ValidatePackageNames(pkgs); err != nil {
+ return nil, err
+ }
+
args := append([]string{"install", ArgsFixBroken}, pkgs...)
if opts == nil {
@@ -102,6 +107,11 @@ func (a *PackageManager) Install(pkgs []string, opts *manager.Options) ([]manage
// Delete removes the specified packages using the snap package manager with the provided options.
func (a *PackageManager) Delete(pkgs []string, opts *manager.Options) ([]manager.PackageInfo, error) {
+ // Validate package names to prevent command injection
+ if err := manager.ValidatePackageNames(pkgs); err != nil {
+ return nil, err
+ }
+
args := append([]string{"remove", ArgsFixBroken}, pkgs...)
if opts == nil {
@@ -153,6 +163,11 @@ func (a *PackageManager) Refresh(opts *manager.Options) error {
// Find searches for packages matching the provided keywords using the snap package manager.
func (a *PackageManager) Find(keywords []string, opts *manager.Options) ([]manager.PackageInfo, error) {
+ // Validate keywords to prevent command injection
+ if err := manager.ValidatePackageNames(keywords); err != nil {
+ return nil, err
+ }
+
args := append([]string{"search"}, keywords...)
cmd := exec.Command("snap", args...)
cmd.Env = ENV_NonInteractive
@@ -189,6 +204,13 @@ func (a *PackageManager) ListUpgradable(opts *manager.Options) ([]manager.Packag
// Upgrade upgrades the specified packages using the snap package manager with the provided options.
func (a *PackageManager) Upgrade(pkgs []string, opts *manager.Options) ([]manager.PackageInfo, error) {
+ // Validate package names to prevent command injection
+ if len(pkgs) > 0 {
+ if err := manager.ValidatePackageNames(pkgs); err != nil {
+ return nil, err
+ }
+ }
+
args := []string{"refresh"}
if len(pkgs) > 0 {
args = append(args, pkgs...)
@@ -244,6 +266,11 @@ func (a *PackageManager) UpgradeAll(opts *manager.Options) ([]manager.PackageInf
// GetPackageInfo retrieves information about the specified package using the snap package manager.
func (a *PackageManager) GetPackageInfo(pkg string, opts *manager.Options) (manager.PackageInfo, error) {
+ // Validate package name to prevent command injection
+ if err := manager.ValidatePackageName(pkg); err != nil {
+ return manager.PackageInfo{}, err
+ }
+
cmd := exec.Command("snap", "info", pkg)
cmd.Env = ENV_NonInteractive
out, err := cmd.Output()
diff --git a/manager/yum/utils.go b/manager/yum/utils.go
index 979dd0b..15db729 100644
--- a/manager/yum/utils.go
+++ b/manager/yum/utils.go
@@ -462,6 +462,11 @@ func ParseAutoRemoveOutput(msg string, opts *manager.Options) []manager.PackageI
// checkRpmInstallationStatus uses rpm -q to check which packages are installed
// Returns a map of installed package names to their PackageInfo using the provided CommandRunner
func checkRpmInstallationStatus(packageNames []string, runner manager.CommandRunner) (map[string]manager.PackageInfo, error) {
+ // Validate package names to prevent command injection
+ if err := manager.ValidatePackageNames(packageNames); err != nil {
+ return nil, err
+ }
+
installedPackages := make(map[string]manager.PackageInfo)
// Check if rpm command is available by trying to run rpm --version
diff --git a/manager/yum/yum.go b/manager/yum/yum.go
index 91c13c9..1d6557e 100644
--- a/manager/yum/yum.go
+++ b/manager/yum/yum.go
@@ -121,6 +121,11 @@ func (a *PackageManager) GetPackageManager() string {
// - Uses -y flag to automatically answer yes to prompts
// - Respects DryRun, Interactive, and Verbose options
func (a *PackageManager) Install(pkgs []string, opts *manager.Options) ([]manager.PackageInfo, error) {
+ // Validate package names to prevent command injection
+ if err := manager.ValidatePackageNames(pkgs); err != nil {
+ return nil, err
+ }
+
if opts == nil {
opts = &manager.Options{
DryRun: false,
@@ -173,6 +178,11 @@ func (a *PackageManager) Install(pkgs []string, opts *manager.Options) ([]manage
// - Uses -y flag to automatically answer yes to prompts
// - Respects DryRun, Interactive, and Verbose options
func (a *PackageManager) Delete(pkgs []string, opts *manager.Options) ([]manager.PackageInfo, error) {
+ // Validate package names to prevent command injection
+ if err := manager.ValidatePackageNames(pkgs); err != nil {
+ return nil, err
+ }
+
if opts == nil {
opts = &manager.Options{
DryRun: false,
@@ -267,6 +277,11 @@ func (a *PackageManager) Refresh(opts *manager.Options) error {
// - Version: Installed version (if installed) or empty
// - NewVersion: Available version from repositories
func (a *PackageManager) Find(keywords []string, opts *manager.Options) ([]manager.PackageInfo, error) {
+ // Validate keywords to prevent command injection
+ if err := manager.ValidatePackageNames(keywords); err != nil {
+ return nil, err
+ }
+
ctx, cancel := context.WithTimeout(context.Background(), readTimeout)
defer cancel()
@@ -336,6 +351,13 @@ func (a *PackageManager) ListUpgradable(opts *manager.Options) ([]manager.Packag
// Upgrade upgrades the specified packages using the yum package manager.
// Returns PackageInfo for each successfully upgraded package with new version information.
func (a *PackageManager) Upgrade(pkgs []string, opts *manager.Options) ([]manager.PackageInfo, error) {
+ // Validate package names to prevent command injection
+ if len(pkgs) > 0 {
+ if err := manager.ValidatePackageNames(pkgs); err != nil {
+ return nil, err
+ }
+ }
+
if opts == nil {
opts = &manager.Options{
DryRun: false,
@@ -470,6 +492,11 @@ func (a *PackageManager) Clean(opts *manager.Options) error {
// PackageStatusAvailable if under "Available Packages" section
// - PackageManager: "yum"
func (a *PackageManager) GetPackageInfo(pkg string, opts *manager.Options) (manager.PackageInfo, error) {
+ // Validate package name to prevent command injection
+ if err := manager.ValidatePackageName(pkg); err != nil {
+ return manager.PackageInfo{}, err
+ }
+
ctx, cancel := context.WithTimeout(context.Background(), readTimeout)
defer cancel()