Files
zeekay 7cf87b230b node-unified is the node
The module already called itself github.com/luxfi/node while containing no node
in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and
the Go column of the matrix measured a VM plugin. A repo that fetches the thing
it claims to be has not replaced it, so the Go sources move in — 1515 files,
the daemon and everything under it — and the build reaches outside this module
for nothing.

Rust and C++ sit beside it unchanged and are still measured against it by the
differential. That is the point of one repo: the reference implementation and
the two ports are the same checkout, so parity is a thing you can run rather
than a thing you assert.

Verified: builds, and the closure carries no lux-private — checked at build time
rather than believed. The daemon starts twelve chains on a local network
(P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
2026-09-07 16:15:25 -07:00

304 lines
7.8 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package security provides security validation and protection utilities
package security
import (
"crypto/subtle"
"fmt"
"math"
"net"
"path/filepath"
"regexp"
"strings"
)
// InputValidator provides methods for validating and sanitizing user input
type InputValidator struct {
// MaxStringLength is the maximum allowed string length
MaxStringLength int
// AllowedPathPrefixes are the allowed path prefixes for file operations
AllowedPathPrefixes []string
}
// NewInputValidator creates a new input validator with default settings
func NewInputValidator() *InputValidator {
return &InputValidator{
MaxStringLength: 10000,
AllowedPathPrefixes: []string{"/tmp", "/var/tmp"},
}
}
// ValidateStringLength checks if a string length is within acceptable bounds
func (v *InputValidator) ValidateStringLength(s string) error {
if len(s) > v.MaxStringLength {
return fmt.Errorf("string length %d exceeds maximum allowed %d", len(s), v.MaxStringLength)
}
return nil
}
// ValidateFilePath validates a file path for security issues
func (v *InputValidator) ValidateFilePath(path string) error {
// Clean the path to resolve . and .. elements
cleanPath := filepath.Clean(path)
// Check for path traversal attempts
if strings.Contains(path, "..") {
return fmt.Errorf("path traversal detected in path: %s", path)
}
// Ensure the path is absolute
if !filepath.IsAbs(cleanPath) {
return fmt.Errorf("path must be absolute: %s", cleanPath)
}
// Check if path is within allowed prefixes
allowed := false
for _, prefix := range v.AllowedPathPrefixes {
if strings.HasPrefix(cleanPath, prefix) {
allowed = true
break
}
}
if !allowed && len(v.AllowedPathPrefixes) > 0 {
return fmt.Errorf("path %s is not within allowed prefixes", cleanPath)
}
return nil
}
// ValidateIPAddress validates an IP address or hostname
func (v *InputValidator) ValidateIPAddress(addr string) error {
// Check if it's a hostname:port combination and extract the host
host, _, err := net.SplitHostPort(addr)
if err == nil {
addr = host
}
// First check if it looks like an IPv4 address and validate properly
if strings.Count(addr, ".") == 3 && !strings.Contains(addr, ":") {
parts := strings.Split(addr, ".")
allNumeric := true
for _, part := range parts {
if part == "" {
allNumeric = false
break
}
for _, ch := range part {
if ch < '0' || ch > '9' {
allNumeric = false
break
}
}
if !allNumeric {
break
}
}
// Only validate as IPv4 if all parts are numeric
if allNumeric {
for _, part := range parts {
num := 0
if _, err := fmt.Sscanf(part, "%d", &num); err != nil || num < 0 || num > 255 {
return fmt.Errorf("invalid IPv4 address: %s", addr)
}
}
}
}
// Check if it's a valid IP address
ip := net.ParseIP(addr)
if ip != nil {
return nil
}
// Basic hostname validation
if len(addr) > 253 {
return fmt.Errorf("hostname too long: %d characters", len(addr))
}
// Check for valid hostname pattern
hostnameRegex := regexp.MustCompile(`^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$`)
if !hostnameRegex.MatchString(addr) {
return fmt.Errorf("invalid hostname or IP address: %s", addr)
}
return nil
}
// ValidatePort validates a network port number
func (v *InputValidator) ValidatePort(port int) error {
if port < 1 || port > 65535 {
return fmt.Errorf("invalid port number: %d (must be between 1 and 65535)", port)
}
return nil
}
// SafeIntConversion safely converts between integer types checking for overflow
func SafeIntConversion(value int64, bitSize int) (int64, error) {
switch bitSize {
case 8:
if value < math.MinInt8 || value > math.MaxInt8 {
return 0, fmt.Errorf("value %d overflows int8", value)
}
case 16:
if value < math.MinInt16 || value > math.MaxInt16 {
return 0, fmt.Errorf("value %d overflows int16", value)
}
case 32:
if value < math.MinInt32 || value > math.MaxInt32 {
return 0, fmt.Errorf("value %d overflows int32", value)
}
case 64:
// Already int64, no overflow possible
default:
return 0, fmt.Errorf("unsupported bit size: %d", bitSize)
}
return value, nil
}
// SafeUintConversion safely converts to unsigned integer checking for overflow
func SafeUintConversion(value int64, bitSize int) (uint64, error) {
if value < 0 {
return 0, fmt.Errorf("cannot convert negative value %d to unsigned", value)
}
uvalue := uint64(value)
switch bitSize {
case 8:
if uvalue > math.MaxUint8 {
return 0, fmt.Errorf("value %d overflows uint8", value)
}
case 16:
if uvalue > math.MaxUint16 {
return 0, fmt.Errorf("value %d overflows uint16", value)
}
case 32:
if uvalue > math.MaxUint32 {
return 0, fmt.Errorf("value %d overflows uint32", value)
}
case 64:
// Already uint64, no overflow possible from positive int64
default:
return 0, fmt.Errorf("unsupported bit size: %d", bitSize)
}
return uvalue, nil
}
// ConstantTimeCompare performs constant-time comparison of two strings
// Returns true if strings are equal, false otherwise
func ConstantTimeCompare(a, b string) bool {
if len(a) != len(b) {
return false
}
aBytes := []byte(a)
bBytes := []byte(b)
return subtle.ConstantTimeCompare(aBytes, bBytes) == 1
}
// SanitizeSQL performs basic SQL injection prevention
// This is a basic implementation - use prepared statements for production
func SanitizeSQL(input string) string {
// Remove or escape potentially dangerous characters
replacer := strings.NewReplacer(
";", "",
"--", "",
"/*", "",
"*/", "",
"xp_", "",
"sp_", "",
"'", "''",
"\"", "\"\"",
)
return replacer.Replace(input)
}
// ValidateAlphanumeric checks if a string contains only alphanumeric characters
func ValidateAlphanumeric(s string) error {
alphanumeric := regexp.MustCompile(`^[a-zA-Z0-9]+$`)
if !alphanumeric.MatchString(s) {
return fmt.Errorf("string contains non-alphanumeric characters")
}
return nil
}
// ValidateEmail performs basic email validation
func ValidateEmail(email string) error {
// Basic email regex pattern
emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
if !emailRegex.MatchString(email) {
return fmt.Errorf("invalid email format")
}
if len(email) > 254 { // RFC 5321
return fmt.Errorf("email address too long")
}
return nil
}
// ValidateURL performs basic URL validation
func ValidateURL(url string) error {
// Check for common URL injection patterns
dangerous := []string{
"javascript:",
"data:",
"vbscript:",
"file:",
"<script",
"onclick",
"onerror",
}
lowerURL := strings.ToLower(url)
for _, pattern := range dangerous {
if strings.Contains(lowerURL, pattern) {
return fmt.Errorf("potentially dangerous URL pattern detected: %s", pattern)
}
}
// Basic URL format check
if !strings.HasPrefix(lowerURL, "http://") && !strings.HasPrefix(lowerURL, "https://") {
return fmt.Errorf("URL must start with http:// or https://")
}
return nil
}
// RateLimiter provides basic rate limiting functionality
type RateLimiter struct {
requests map[string][]int64
limit int
window int64 // in seconds
}
// NewRateLimiter creates a new rate limiter
func NewRateLimiter(limit int, windowSeconds int64) *RateLimiter {
return &RateLimiter{
requests: make(map[string][]int64),
limit: limit,
window: windowSeconds,
}
}
// Allow checks if a request should be allowed based on rate limits
func (r *RateLimiter) Allow(clientID string, now int64) bool {
// Clean old requests
validRequests := []int64{}
for _, reqTime := range r.requests[clientID] {
if now-reqTime <= r.window {
validRequests = append(validRequests, reqTime)
}
}
if len(validRequests) >= r.limit {
r.requests[clientID] = validRequests
return false
}
validRequests = append(validRequests, now)
r.requests[clientID] = validRequests
return true
}