mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-04-30 14:56:21 +02:00
initial: Syntrex extraction from sentinel-community (615 files)
This commit is contained in:
commit
2c50c993b1
175 changed files with 32396 additions and 0 deletions
188
internal/domain/pivot/executor.go
Normal file
188
internal/domain/pivot/executor.go
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
// Package pivot — Execution Layer for Pivot Engine (v3.8 Strike Force).
|
||||
// Executes system commands in ZERO-G mode after Oracle verification.
|
||||
// All executions are logged to decisions.log (tamper-evident).
|
||||
package pivot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxOutputBytes caps command output to prevent memory exhaustion.
|
||||
MaxOutputBytes = 64 * 1024 // 64KB
|
||||
// DefaultTimeout for command execution.
|
||||
DefaultTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
// ExecResult holds the result of a command execution.
|
||||
type ExecResult struct {
|
||||
Command string `json:"command"`
|
||||
Args []string `json:"args"`
|
||||
Stdout string `json:"stdout"`
|
||||
Stderr string `json:"stderr"`
|
||||
ExitCode int `json:"exit_code"`
|
||||
Duration time.Duration `json:"duration"`
|
||||
OraclePass bool `json:"oracle_pass"`
|
||||
ZeroGMode bool `json:"zero_g_mode"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// OracleGate verifies actions. Implemented by oracle.Oracle.
|
||||
type OracleGate interface {
|
||||
VerifyAction(action string) (verdict string, reason string)
|
||||
}
|
||||
|
||||
// Executor runs system commands under Pivot Engine control.
|
||||
type Executor struct {
|
||||
rlmDir string
|
||||
oracle OracleGate
|
||||
recorder DecisionRecorder
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// NewExecutor creates a new command executor.
|
||||
func NewExecutor(rlmDir string, oracle OracleGate, recorder DecisionRecorder) *Executor {
|
||||
return &Executor{
|
||||
rlmDir: rlmDir,
|
||||
oracle: oracle,
|
||||
recorder: recorder,
|
||||
timeout: DefaultTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
// SetTimeout overrides the default execution timeout.
|
||||
func (e *Executor) SetTimeout(d time.Duration) {
|
||||
if d > 0 {
|
||||
e.timeout = d
|
||||
}
|
||||
}
|
||||
|
||||
// Execute runs a command string after ZERO-G and Oracle verification.
|
||||
// Returns ExecResult with full audit trail.
|
||||
func (e *Executor) Execute(cmdLine string) ExecResult {
|
||||
result := ExecResult{
|
||||
Command: cmdLine,
|
||||
}
|
||||
|
||||
// Gate 1: ZERO-G mode check.
|
||||
zeroG := e.isZeroG()
|
||||
result.ZeroGMode = zeroG
|
||||
if !zeroG {
|
||||
result.Error = "BLOCKED: ZERO-G mode required for command execution"
|
||||
e.record("EXEC_BLOCKED", fmt.Sprintf("cmd='%s' reason=not_zero_g", truncate(cmdLine, 60)))
|
||||
return result
|
||||
}
|
||||
|
||||
// Gate 2: Oracle verification.
|
||||
if e.oracle != nil {
|
||||
verdict, reason := e.oracle.VerifyAction(cmdLine)
|
||||
result.OraclePass = (verdict == "ALLOW")
|
||||
if verdict == "DENY" {
|
||||
result.Error = fmt.Sprintf("BLOCKED by Oracle: %s", reason)
|
||||
e.record("EXEC_DENIED", fmt.Sprintf("cmd='%s' reason=%s", truncate(cmdLine, 60), reason))
|
||||
return result
|
||||
}
|
||||
} else {
|
||||
result.OraclePass = true // No oracle = passthrough in ZERO-G
|
||||
}
|
||||
|
||||
// Parse command.
|
||||
parts := parseCommand(cmdLine)
|
||||
if len(parts) == 0 {
|
||||
result.Error = "empty command"
|
||||
return result
|
||||
}
|
||||
|
||||
result.Command = parts[0]
|
||||
if len(parts) > 1 {
|
||||
result.Args = parts[1:]
|
||||
}
|
||||
|
||||
// Execute with timeout.
|
||||
e.record("EXEC_START", fmt.Sprintf("cmd='%s' args=%v timeout=%s", result.Command, result.Args, e.timeout))
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, result.Command, result.Args...)
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &limitedWriter{w: &stdout, limit: MaxOutputBytes}
|
||||
cmd.Stderr = &limitedWriter{w: &stderr, limit: MaxOutputBytes}
|
||||
|
||||
start := time.Now()
|
||||
err := cmd.Run()
|
||||
result.Duration = time.Since(start)
|
||||
result.Stdout = stdout.String()
|
||||
result.Stderr = stderr.String()
|
||||
|
||||
if err != nil {
|
||||
result.Error = err.Error()
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
result.ExitCode = exitErr.ExitCode()
|
||||
} else {
|
||||
result.ExitCode = -1
|
||||
}
|
||||
}
|
||||
|
||||
e.record("EXEC_COMPLETE", fmt.Sprintf("cmd='%s' exit=%d duration=%s stdout_len=%d",
|
||||
result.Command, result.ExitCode, result.Duration, len(result.Stdout)))
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// isZeroG checks if .sentinel_leash contains ZERO-G.
|
||||
func (e *Executor) isZeroG() bool {
|
||||
leashPath := filepath.Join(e.rlmDir, "..", ".sentinel_leash")
|
||||
data, err := os.ReadFile(leashPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(string(data), "ZERO-G")
|
||||
}
|
||||
|
||||
func (e *Executor) record(decision, reason string) {
|
||||
if e.recorder != nil {
|
||||
e.recorder.RecordDecision("PIVOT", decision, reason)
|
||||
}
|
||||
}
|
||||
|
||||
// parseCommand splits a command string into parts (respects quotes).
|
||||
func parseCommand(cmdLine string) []string {
|
||||
// Strip "stealth " prefix if present (Mimicry passthrough).
|
||||
cmdLine = strings.TrimPrefix(cmdLine, "stealth ")
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// On Windows, wrap in cmd /C.
|
||||
return []string{"cmd", "/C", cmdLine}
|
||||
}
|
||||
// On Linux/Mac, use sh -c.
|
||||
return []string{"sh", "-c", cmdLine}
|
||||
}
|
||||
|
||||
// limitedWriter caps the amount of data written.
|
||||
type limitedWriter struct {
|
||||
w *bytes.Buffer
|
||||
limit int
|
||||
written int
|
||||
}
|
||||
|
||||
func (lw *limitedWriter) Write(p []byte) (int, error) {
|
||||
remaining := lw.limit - lw.written
|
||||
if remaining <= 0 {
|
||||
return len(p), nil // Silently discard.
|
||||
}
|
||||
if len(p) > remaining {
|
||||
p = p[:remaining]
|
||||
}
|
||||
n, err := lw.w.Write(p)
|
||||
lw.written += n
|
||||
return n, err
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue