Files
CyberStrikeAI/internal/processguard/guard.go
T

90 lines
2.4 KiB
Go

// Package processguard provides OS containment and out-of-process crash cleanup.
// It is intentionally independent of the Agent/MCP packages so it can be
// cross-compiled and exercised without starting the application.
package processguard
import (
"context"
"fmt"
"os/exec"
"sync"
)
type Options struct {
Mode string `yaml:"mode" json:"mode"` // auto, required, process_group
CgroupRoot string `yaml:"cgroup_root" json:"cgroup_root"`
MaxProcesses int `yaml:"max_processes" json:"max_processes"`
MemoryMaxBytes int64 `yaml:"memory_max_bytes" json:"memory_max_bytes"`
CPUQuotaMicros int64 `yaml:"cpu_quota_micros" json:"cpu_quota_micros"` // per 100000 us
}
// Prepared commands must call Commit after Start and always call Dispose.
// Commit releases the Unix fallback launch gate only after watchdog ownership
// is acknowledged. Strong backends assign containment atomically at creation.
type Launch struct {
Commit func() error
Dispose func()
}
type Group interface {
Name() string
Prepare(*exec.Cmd) (*Launch, error)
Release(int) error
Close(context.Context) error
}
var configured = struct {
sync.RWMutex
opts Options
}{opts: Options{Mode: "auto", MaxProcesses: 256, MemoryMaxBytes: 2 << 30}}
func normalize(o Options) (Options, error) {
if o.Mode == "" {
o.Mode = "auto"
}
if o.Mode != "auto" && o.Mode != "required" && o.Mode != "process_group" {
return o, fmt.Errorf("invalid process isolation mode %q", o.Mode)
}
if o.MaxProcesses == 0 {
o.MaxProcesses = 256
}
if o.MemoryMaxBytes == 0 {
o.MemoryMaxBytes = 2 << 30
}
if o.MaxProcesses < 1 || o.MaxProcesses > 65535 || o.MemoryMaxBytes < 0 || o.CPUQuotaMicros < 0 {
return o, fmt.Errorf("invalid process isolation resource limits")
}
return o, nil
}
// Configure validates deployment before accepting any tasks. An explicit root
// or required mode fails closed; it never silently falls back after an error.
func Configure(o Options) error {
var err error
o, err = normalize(o)
if err != nil {
return err
}
if err = configurePlatform(&o); err != nil {
return err
}
configured.Lock()
configured.opts = o
configured.Unlock()
return nil
}
func New(id string) (Group, error) {
configured.RLock()
o := configured.opts
configured.RUnlock()
return NewWithOptions(id, o)
}
func NewWithOptions(id string, o Options) (Group, error) {
var err error
o, err = normalize(o)
if err != nil {
return nil, err
}
return newPlatformGroup(id, o)
}