mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-05-08 11:02:37 +02:00
Release prep: 54 engines, self-hosted signatures, i18n, dashboard updates
This commit is contained in:
parent
694e32be26
commit
41cbfd6e0a
178 changed files with 36008 additions and 399 deletions
299
internal/infrastructure/antitamper/antitamper.go
Normal file
299
internal/infrastructure/antitamper/antitamper.go
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
// Package antitamper implements SEC-005 Anti-Tamper Protection.
|
||||
//
|
||||
// Provides runtime protection against:
|
||||
// - ptrace/debugger attachment to SOC processes
|
||||
// - memory dump (process_vm_readv)
|
||||
// - binary modification detection via SHA-256 integrity checks
|
||||
// - environment variable tampering
|
||||
//
|
||||
// On Linux: uses prctl(PR_SET_DUMPABLE, 0) and self-ptrace detection.
|
||||
// On Windows: uses IsDebuggerPresent() and NtQueryInformationProcess.
|
||||
// Cross-platform: binary hash verification and env integrity checks.
|
||||
package antitamper
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TamperType classifies the tampering attempt.
|
||||
type TamperType string
|
||||
|
||||
const (
|
||||
TamperDebugger TamperType = "debugger_attached"
|
||||
TamperPtrace TamperType = "ptrace_attempt"
|
||||
TamperBinaryMod TamperType = "binary_modified"
|
||||
TamperEnvTamper TamperType = "env_tampering"
|
||||
TamperMemoryDump TamperType = "memory_dump"
|
||||
|
||||
// CheckInterval for periodic integrity verification.
|
||||
DefaultCheckInterval = 5 * time.Minute
|
||||
)
|
||||
|
||||
// TamperEvent records a detected tampering attempt.
|
||||
type TamperEvent struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Type TamperType `json:"type"`
|
||||
Detail string `json:"detail"`
|
||||
Severity string `json:"severity"`
|
||||
PID int `json:"pid"`
|
||||
Binary string `json:"binary,omitempty"`
|
||||
}
|
||||
|
||||
// TamperHandler is called when tampering is detected.
|
||||
type TamperHandler func(event TamperEvent)
|
||||
|
||||
// Shield provides anti-tamper protection for SOC processes.
|
||||
type Shield struct {
|
||||
mu sync.RWMutex
|
||||
binaryPath string
|
||||
binaryHash string // SHA-256 at startup
|
||||
envSnapshot map[string]string
|
||||
handlers []TamperHandler
|
||||
logger *slog.Logger
|
||||
stats ShieldStats
|
||||
}
|
||||
|
||||
// ShieldStats tracks anti-tamper metrics.
|
||||
type ShieldStats struct {
|
||||
mu sync.Mutex
|
||||
TotalChecks int64 `json:"total_checks"`
|
||||
TamperDetected int64 `json:"tamper_detected"`
|
||||
DebuggerBlocked int64 `json:"debugger_blocked"`
|
||||
BinaryIntegrity bool `json:"binary_integrity"`
|
||||
LastCheck time.Time `json:"last_check"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
}
|
||||
|
||||
// NewShield creates a new anti-tamper shield.
|
||||
// Takes a snapshot of the binary hash and critical env vars at startup.
|
||||
func NewShield() (*Shield, error) {
|
||||
binaryPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("antitamper: get executable: %w", err)
|
||||
}
|
||||
|
||||
hash, err := hashFile(binaryPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("antitamper: hash binary: %w", err)
|
||||
}
|
||||
|
||||
// Snapshot critical environment variables.
|
||||
criticalEnvs := []string{
|
||||
"SOC_DB_PATH", "SOC_JWT_SECRET", "SOC_GUARD_POLICY",
|
||||
"GOMEMLIMIT", "SOC_AUDIT_DIR", "SOC_PORT",
|
||||
}
|
||||
envSnap := make(map[string]string)
|
||||
for _, key := range criticalEnvs {
|
||||
envSnap[key] = os.Getenv(key)
|
||||
}
|
||||
|
||||
shield := &Shield{
|
||||
binaryPath: binaryPath,
|
||||
binaryHash: hash,
|
||||
envSnapshot: envSnap,
|
||||
logger: slog.Default().With("component", "sec-005-antitamper"),
|
||||
stats: ShieldStats{
|
||||
BinaryIntegrity: true,
|
||||
StartedAt: time.Now(),
|
||||
},
|
||||
}
|
||||
|
||||
// Platform-specific initialization (disable core dumps, set non-dumpable).
|
||||
shield.platformInit()
|
||||
|
||||
shield.logger.Info("anti-tamper shield initialized",
|
||||
"binary", binaryPath,
|
||||
"hash", hash[:16]+"...",
|
||||
"env_keys", len(envSnap),
|
||||
)
|
||||
|
||||
return shield, nil
|
||||
}
|
||||
|
||||
// OnTamper registers a handler for tampering events.
|
||||
func (s *Shield) OnTamper(h TamperHandler) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.handlers = append(s.handlers, h)
|
||||
}
|
||||
|
||||
// CheckBinaryIntegrity verifies the running binary hasn't been modified.
|
||||
func (s *Shield) CheckBinaryIntegrity() *TamperEvent {
|
||||
s.stats.mu.Lock()
|
||||
s.stats.TotalChecks++
|
||||
s.stats.LastCheck = time.Now()
|
||||
s.stats.mu.Unlock()
|
||||
|
||||
currentHash, err := hashFile(s.binaryPath)
|
||||
if err != nil {
|
||||
event := TamperEvent{
|
||||
Timestamp: time.Now(),
|
||||
Type: TamperBinaryMod,
|
||||
Detail: fmt.Sprintf("cannot read binary for hash check: %v", err),
|
||||
Severity: "HIGH",
|
||||
PID: os.Getpid(),
|
||||
Binary: s.binaryPath,
|
||||
}
|
||||
s.recordTamper(event)
|
||||
return &event
|
||||
}
|
||||
|
||||
if currentHash != s.binaryHash {
|
||||
s.stats.mu.Lock()
|
||||
s.stats.BinaryIntegrity = false
|
||||
s.stats.mu.Unlock()
|
||||
|
||||
event := TamperEvent{
|
||||
Timestamp: time.Now(),
|
||||
Type: TamperBinaryMod,
|
||||
Detail: fmt.Sprintf("binary modified! expected=%s got=%s",
|
||||
truncHash(s.binaryHash), truncHash(currentHash)),
|
||||
Severity: "CRITICAL",
|
||||
PID: os.Getpid(),
|
||||
Binary: s.binaryPath,
|
||||
}
|
||||
s.recordTamper(event)
|
||||
return &event
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckEnvIntegrity verifies critical environment variables haven't changed.
|
||||
func (s *Shield) CheckEnvIntegrity() *TamperEvent {
|
||||
s.stats.mu.Lock()
|
||||
s.stats.TotalChecks++
|
||||
s.stats.mu.Unlock()
|
||||
|
||||
for key, originalValue := range s.envSnapshot {
|
||||
current := os.Getenv(key)
|
||||
if current != originalValue {
|
||||
event := TamperEvent{
|
||||
Timestamp: time.Now(),
|
||||
Type: TamperEnvTamper,
|
||||
Detail: fmt.Sprintf("env %s changed: original=%q current=%q",
|
||||
key, originalValue, current),
|
||||
Severity: "HIGH",
|
||||
PID: os.Getpid(),
|
||||
}
|
||||
s.recordTamper(event)
|
||||
return &event
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckDebugger checks if a debugger is attached.
|
||||
// Platform-specific implementation in antitamper_*.go.
|
||||
func (s *Shield) CheckDebugger() *TamperEvent {
|
||||
s.stats.mu.Lock()
|
||||
s.stats.TotalChecks++
|
||||
s.stats.mu.Unlock()
|
||||
|
||||
if s.isDebuggerAttached() {
|
||||
s.stats.mu.Lock()
|
||||
s.stats.DebuggerBlocked++
|
||||
s.stats.mu.Unlock()
|
||||
|
||||
event := TamperEvent{
|
||||
Timestamp: time.Now(),
|
||||
Type: TamperDebugger,
|
||||
Detail: "debugger detected attached to SOC process",
|
||||
Severity: "CRITICAL",
|
||||
PID: os.Getpid(),
|
||||
Binary: s.binaryPath,
|
||||
}
|
||||
s.recordTamper(event)
|
||||
return &event
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunAllChecks performs all anti-tamper checks at once.
|
||||
func (s *Shield) RunAllChecks() []TamperEvent {
|
||||
var events []TamperEvent
|
||||
|
||||
if e := s.CheckDebugger(); e != nil {
|
||||
events = append(events, *e)
|
||||
}
|
||||
if e := s.CheckBinaryIntegrity(); e != nil {
|
||||
events = append(events, *e)
|
||||
}
|
||||
if e := s.CheckEnvIntegrity(); e != nil {
|
||||
events = append(events, *e)
|
||||
}
|
||||
|
||||
return events
|
||||
}
|
||||
|
||||
// BinaryHash returns the expected binary hash (taken at startup).
|
||||
func (s *Shield) BinaryHash() string {
|
||||
return s.binaryHash
|
||||
}
|
||||
|
||||
// Stats returns current shield metrics.
|
||||
func (s *Shield) Stats() ShieldStats {
|
||||
s.stats.mu.Lock()
|
||||
defer s.stats.mu.Unlock()
|
||||
return ShieldStats{
|
||||
TotalChecks: s.stats.TotalChecks,
|
||||
TamperDetected: s.stats.TamperDetected,
|
||||
DebuggerBlocked: s.stats.DebuggerBlocked,
|
||||
BinaryIntegrity: s.stats.BinaryIntegrity,
|
||||
LastCheck: s.stats.LastCheck,
|
||||
StartedAt: s.stats.StartedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// recordTamper updates stats and notifies handlers.
|
||||
func (s *Shield) recordTamper(event TamperEvent) {
|
||||
s.stats.mu.Lock()
|
||||
s.stats.TamperDetected++
|
||||
s.stats.mu.Unlock()
|
||||
|
||||
s.logger.Error("TAMPER DETECTED",
|
||||
"type", event.Type,
|
||||
"detail", event.Detail,
|
||||
"severity", event.Severity,
|
||||
"pid", event.PID,
|
||||
)
|
||||
|
||||
s.mu.RLock()
|
||||
handlers := s.handlers
|
||||
s.mu.RUnlock()
|
||||
|
||||
for _, h := range handlers {
|
||||
h(event)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
func hashFile(path string) (string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func truncHash(h string) string {
|
||||
if len(h) > 16 {
|
||||
return h[:16]
|
||||
}
|
||||
return h
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue