safety

package
v1.8.16 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 1, 2026 License: MPL-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package safety provides protection mechanisms to prevent self-lockout and ensure system stability during firewall operations.

Features

The safety package implements several protection mechanisms:

  • System IP Detection: Auto-detects IPs that must never be blocked (server IP, gateway, DNS servers, active SSH connections)
  • Memory Limits: Monitors and enforces memory usage limits
  • File Safety: Validates file operations to prevent data loss
  • Resource Limits: Prevents runaway processes

System IP Detection

The Detect functions analyze the system to find critical IPs:

ips, err := safety.DetectSystemIPs()
// Returns: server IPs, gateway IP, DNS servers, active connections

These IPs are automatically added to the whitelist to prevent lockout.

Memory Protection

Memory limits prevent the watchdog and feeds from consuming excessive RAM:

if safety.IsMemoryPressureHigh() {
    // Trigger garbage collection or reduce operations
}

File Safety

File operations are validated to ensure they target allowed paths:

if safety.IsPathSafe(filepath) {
    // Proceed with write
}

Index

Constants

View Source
const (
	// MaxCIDRsWarning is the threshold for warning users about large CIDR sets
	MaxCIDRsWarning = 50000

	// BytesPerCIDREstimate is the estimated memory per CIDR during merge operations
	// Includes string storage + parsing + temporary merge buffers
	BytesPerCIDREstimate = 300

	// BanAgeThresholdDays is how long a permanent ban must be inactive before eviction
	BanAgeThresholdDays = 30
)

CIDR loading limits to prevent memory exhaustion

View Source
const (
	// ProtectionStateDir is where protection state is stored
	ProtectionStateDir = "/var/lib/nftban/state"
	// ProtectionStateFile is the protection state JSON file
	ProtectionStateFile = "/var/lib/nftban/state/protection.json"
)
View Source
const (
	// PermanentBansDir is where permanent ban tracking is stored
	PermanentBansDir = "/var/lib/nftban/state"
	// PermanentBansFile tracks permanent bans with metadata
	PermanentBansFile = "/var/lib/nftban/state/permanent_bans.json"
)

Variables

View Source
var ErrInsecureDirectory = errors.New("target directory has insecure permissions (world-writable)")

ErrInsecureDirectory is returned when target directory has insecure permissions

View Source
var ErrPathTraversal = errors.New("path traversal detected")

ErrPathTraversal is returned when path traversal is detected

View Source
var ErrSymlinkDetected = errors.New("symlink detected in path (TOCTOU protection)")

ErrSymlinkDetected is returned when a symlink is detected in the path

Functions

func CanAllocate added in v1.2.3

func CanAllocate(estimatedBytes int64) bool

CanAllocate checks if allocating the given bytes is safe Returns false if allocation would exceed available memory budget

func ClearProtectionState added in v1.8.16

func ClearProtectionState() error

ClearProtectionState removes protection state (called when sync succeeds fully)

func FormatBytes

func FormatBytes(bytes int64) string

FormatBytes converts bytes to human-readable format

func GetEvictableBans added in v1.8.16

func GetEvictableBans(maxCount int) ([]string, error)

GetEvictableBans returns IPs that can be evicted (old, unprotected) Returns at most maxCount IPs, oldest first

func GetMaxCIDRsHard added in v1.8.16

func GetMaxCIDRsHard() int

GetMaxCIDRsHard returns the dynamic CIDR limit based on server tier Small servers (≤4GB): 75,000 max CIDRs Medium servers (4-8GB): 100,000 max CIDRs Large servers (>8GB): 150,000 max CIDRs

func GetMaxCIDRsHardWithTier added in v1.8.16

func GetMaxCIDRsHardWithTier() (limit int, tier string)

GetMaxCIDRsHardWithTier returns both the limit and tier name for logging

func GetMemoryBudget added in v1.2.3

func GetMemoryBudget() int64

GetMemoryBudget calculates a safe memory budget based on server profile Takes into account CPU cores, total RAM, and control panel presence Returns a value that leaves headroom for the OS, panel, and other processes

func GetPermanentBanStats added in v1.8.16

func GetPermanentBanStats() (total, protected, evictable int, err error)

GetPermanentBanStats returns stats about permanent bans

func GetProfileDescription added in v1.2.3

func GetProfileDescription() string

GetProfileDescription returns a human-readable description of the server profile

func GetProtectionSummary added in v1.8.16

func GetProtectionSummary() string

GetProtectionSummary returns a human-readable protection status

func GetResourceLimits added in v1.2.3

func GetResourceLimits() (memBudget int64, maxWorkers int)

GetResourceLimits calculates appropriate resource limits based on server profile Returns memory budget and max concurrent workers

func InitCPU

func InitCPU(lim Limits)

InitCPU sets GOMAXPROCS based on config This prevents the Go server from consuming all CPU cores

func InitMemory

func InitMemory(lim Limits)

InitMemory sets memory limit based on server profile and optional overrides Uses runtime/debug SetMemoryLimit (Go 1.19+)

The memory budget is calculated by GetResourceLimits() which considers:

  • Server RAM size (applies tier caps: ≤4GB→384MB, 4-8GB→512MB, >8GB→1GB)
  • Control panel presence (20% for panel servers, 35% for non-panel)
  • Minimum 64MB floor

Environment variable NFTBAN_MAX_MEMORY_BYTES can override this for special cases.

func IsProtectionActive added in v1.8.16

func IsProtectionActive() bool

IsProtectionActive returns true if memory protection is currently active

func IsSymlink(path string) (bool, error)

IsSymlink checks if a path is a symlink without following it

func RecordProtectionTriggered added in v1.8.16

func RecordProtectionTriggered(feedsSkipped bool, geobanSkipped bool, feedsCIDRs int, geobanCIDRs int, feedsMem int64, geobanMem int64) error

RecordProtectionTriggered writes protection state when feeds or geoban are skipped

func ReloadMemoryLimits added in v1.0.28

func ReloadMemoryLimits()

ReloadMemoryLimits reloads limits from environment (for testing/config changes)

func RemovePermanentBan added in v1.8.16

func RemovePermanentBan(ip string) error

RemovePermanentBan removes a ban from tracking

func SafeAppendFile added in v1.0.28

func SafeAppendFile(path string, data []byte, perm os.FileMode) error

SafeAppendFile appends data to a file with TOCTOU protection

func SafeAppendFlags added in v1.0.28

func SafeAppendFlags() int

SafeAppendFlags returns secure flags for appending to files

func SafeCreate added in v1.0.28

func SafeCreate(path string, perm os.FileMode) (*os.File, error)

SafeCreate creates a file with TOCTOU protection - Validates path for symlinks and traversal - Uses O_NOFOLLOW to prevent symlink following - Validates parent directory permissions

func SafeFileFlags added in v1.0.28

func SafeFileFlags() int

SafeFileFlags returns secure flags for file creation O_NOFOLLOW prevents following symlinks (TOCTOU protection) O_EXCL ensures file is created (not opened if exists)

func SafeMkdirAll added in v1.0.28

func SafeMkdirAll(path string, perm os.FileMode) error

SafeMkdirAll creates directories with TOCTOU protection Validates each path component is not a symlink

func SafeOpenFile added in v1.0.28

func SafeOpenFile(path string, flag int, perm os.FileMode) (*os.File, error)

SafeOpenFile opens a file with TOCTOU protection

func SafeWriteFile added in v1.0.28

func SafeWriteFile(path string, data []byte, perm os.FileMode) error

SafeWriteFile writes data to a file with TOCTOU protection Uses atomic write pattern: write to temp, then rename

func SavePermanentBans added in v1.8.16

func SavePermanentBans(state *PermanentBansState) error

SavePermanentBans saves the permanent bans state to disk

func SetBanProtected added in v1.8.16

func SetBanProtected(ip string, protected bool) error

SetBanProtected sets the protected flag for a permanent ban

func ShouldSkipFeeds added in v1.8.16

func ShouldSkipFeeds() bool

ShouldSkipFeeds returns true if memory pressure is critical

func ShouldSkipGeoban added in v1.8.16

func ShouldSkipGeoban() bool

ShouldSkipGeoban returns true if memory pressure is too high for geoban

func TrackPermanentBan added in v1.8.16

func TrackPermanentBan(ip, reason, source string, protected bool) error

TrackPermanentBan adds or updates a permanent ban in tracking

func ValidateDirectory added in v1.0.28

func ValidateDirectory(dir string) error

ValidateDirectory checks if a directory is safe for file creation Returns error if directory is world-writable (o+w)

func ValidatePath added in v1.0.28

func ValidatePath(path string) error

ValidatePath performs security checks on a file path - Rejects symlinks anywhere in the path - Rejects path traversal attempts (../) - Rejects world-writable directories

Types

type CIDRLoadResult added in v1.8.16

type CIDRLoadResult struct {
	Allowed      bool   // Whether to proceed with loading
	Warning      bool   // Whether to show a warning
	Message      string // Human-readable message
	EstimatedMem int64  // Estimated memory usage in bytes
}

CIDRLoadResult describes the result of ValidateCIDRLoad

func ValidateCIDRLoad added in v1.8.16

func ValidateCIDRLoad(ipv4Count, ipv6Count int) CIDRLoadResult

ValidateCIDRLoad checks if loading the given number of CIDRs is safe Returns result with recommendation and warning message if applicable

type Limits

type Limits struct {
	// GOMAXPROCS limit (CPU cores)
	GoMaxProcs int // default: 2

	// Connection limits
	MaxConcurrentConns int // default: 100
	MaxConnsPerIP      int // default: 10

	// Request limits
	RequestTimeoutSec   int   // default: 30
	MaxRequestBodyMB    int   // default: 10
	MaxRequestBodyBytes int64 // computed from MB

	// Rate limiting
	RateLimitPerMin int // default: 60 requests per minute per IP

	// Memory limits
	MaxMemoryPercent int   // default: 20% of available
	MaxMemoryBytes   int64 // default: 512 MiB

	// Logging
	EnableMetrics bool // default: true
}

Limits holds all safety thresholds for the GUI server

func FromEnv

func FromEnv() Limits

FromEnv returns sane defaults that can be overridden via environment variables This matches the pattern from go-feeds/internal/safety/config.go

type MemAvail

type MemAvail struct {
	Total         int64
	Avail         int64
	CgroupLimit   int64
	CgroupCurrent int64
}

MemAvail holds available memory info (cgroup-aware) This matches the pattern from go-feeds/internal/safety/mem.go

func AvailableMem

func AvailableMem() MemAvail

AvailableMem returns available memory (cgroup-aware for containers) This is critical for running in Docker/Kubernetes where cgroup limits apply

type MemoryLimits added in v1.0.28

type MemoryLimits struct {
	// Scorer limits (pkg/suricata/scorer.go)
	ScorerMaxIPs         int // Max unique IPs tracked in scorer (default: 50000)
	ScorerMaxEventsPerIP int // Max event timestamps per IP (default: 100)

	// Analytics limits (pkg/analytics/state.go)
	AnalyticsMaxIPOrigins     int // Max IPs in ipOrigins map (default: 100000)
	AnalyticsMaxIPsPerCountry int // Max IPs per country (default: 10000)

	// Stats cache limits (pkg/suricata/stats/cache.go)
	StatsMaxSIDs          int // Max SIDs tracked (default: 10000)
	StatsMaxSourcesPerSID int // Max unique sources per SID (default: 1000)

	// Queue limits (cli/lib/nftban/helpers/nftban_task_queue.sh)
	QueueMaxPending       int // Max pending tasks (default: 10000)
	QueueDLQAutoRetention int // Auto-purge DLQ entries older than N days (default: 7)
}

MemoryLimits holds caps and TTLs to prevent unbounded memory growth (CWE-400) All limits are configurable via environment variables with sane defaults.

func DefaultMemoryLimits added in v1.0.28

func DefaultMemoryLimits() MemoryLimits

DefaultMemoryLimits returns production-safe defaults

func GetMemoryLimits added in v1.0.28

func GetMemoryLimits() MemoryLimits

GetMemoryLimits returns the global memory limits

type PermanentBan added in v1.8.16

type PermanentBan struct {
	IP        string    `json:"ip"`
	AddedAt   time.Time `json:"added_at"`
	Reason    string    `json:"reason,omitempty"`
	Source    string    `json:"source,omitempty"`
	Protected bool      `json:"protected"` // If true, NEVER evict this IP
}

PermanentBan represents a permanent ban with tracking metadata

type PermanentBansState added in v1.8.16

type PermanentBansState struct {
	UpdatedAt time.Time                `json:"updated_at"`
	Bans      map[string]*PermanentBan `json:"bans"` // keyed by IP
}

PermanentBansState holds all tracked permanent bans

func LoadPermanentBans added in v1.8.16

func LoadPermanentBans() (*PermanentBansState, error)

LoadPermanentBans loads the permanent bans state from disk

type PressureLevel added in v1.8.16

type PressureLevel int

PressureLevel represents the current memory pressure state

const (
	// PressureNormal - below 70% of memory budget, full operations
	PressureNormal PressureLevel = iota
	// PressureWarning - 70-85% of budget, log warning but continue
	PressureWarning
	// PressureHigh - 85-95% of budget, skip geoban refresh
	PressureHigh
	// PressureCritical - >95% of budget, trim cold bans + skip feeds
	PressureCritical
)

func GetMemoryPressureLevel added in v1.8.16

func GetMemoryPressureLevel() PressureLevel

GetMemoryPressureLevel returns the current memory pressure level

func (PressureLevel) String added in v1.8.16

func (p PressureLevel) String() string

String returns the pressure level name

type ProtectionState added in v1.8.16

type ProtectionState struct {
	Timestamp       string `json:"timestamp"`
	FeedsSkipped    bool   `json:"feeds_skipped"`
	GeobanSkipped   bool   `json:"geoban_skipped"`
	FeedsCIDRCount  int    `json:"feeds_cidr_count,omitempty"`
	GeobanCIDRCount int    `json:"geoban_cidr_count,omitempty"`
	FeedsMemNeeded  int64  `json:"feeds_mem_needed,omitempty"`
	GeobanMemNeeded int64  `json:"geoban_mem_needed,omitempty"`
	AvailableMemory int64  `json:"available_memory"`
	Reason          string `json:"reason"`
}

ProtectionState tracks what was skipped due to memory limits

func GetProtectionState added in v1.8.16

func GetProtectionState() (*ProtectionState, error)

GetProtectionState reads current protection state

type ServerProfile added in v1.2.3

type ServerProfile struct {
	CPUCores        int    // Number of CPU cores
	TotalRAM        int64  // Total RAM in bytes
	AvailRAM        int64  // Available RAM in bytes
	HasControlPanel bool   // Whether a control panel is detected
	PanelType       string // "cpanel", "directadmin", "plesk", "cyberpanel", "none"
}

ServerProfile describes the server's resource characteristics

func DetectServerProfile added in v1.2.3

func DetectServerProfile() ServerProfile

DetectServerProfile analyzes the server and returns its resource profile

type SystemIPs

type SystemIPs struct {
	ServerIPs     []net.IP    // All server interface IPs
	CurrentUserIP net.IP      // IP of current SSH connection
	GatewayIPs    []net.IP    // Default gateway
	DNSServers    []net.IP    // DNS servers from /etc/resolv.conf
	LoopbackCIDRs []net.IPNet // 127.0.0.0/8, ::1/128
}

SystemIPs holds all critical IPs that must NEVER be blocked

func DetectSystemIPs

func DetectSystemIPs() (*SystemIPs, error)

DetectSystemIPs auto-detects all critical IPs that must be whitelisted

func (*SystemIPs) GetAllIPs

func (s *SystemIPs) GetAllIPs() []net.IP

GetAllIPs returns all IPs as a flat list

func (*SystemIPs) GetAllIPsWithCIDRs

func (s *SystemIPs) GetAllIPsWithCIDRs() ([]net.IP, []net.IPNet)

GetAllIPsWithCIDRs returns all IPs including loopback CIDRs

func (*SystemIPs) PrintSystemIPs

func (s *SystemIPs) PrintSystemIPs()

PrintSystemIPs displays all detected IPs

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL