Add tiered LOTL mining onion and fleet recon so agents can fallback across execution tiers while operators see spread and vuln posture in Crucible. Includes triple-onion chain, spread cred graph, and full Go/TS/E2E test validation.
This commit is contained in:
186
agent/miner/triple_onion_test.go
Normal file
186
agent/miner/triple_onion_test.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package miner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"crypto-miner-agent/config"
|
||||
)
|
||||
|
||||
func TestEvaluateTripleOnionGatesPatchFirstCritical(t *testing.T) {
|
||||
policy := DefaultTripleOnionPolicy()
|
||||
recon := ReconSnapshot{CriticalExposed: 1, RiskScore: 25}
|
||||
|
||||
gate := EvaluateTripleOnionGates(policy, recon)
|
||||
if !gate.PatchFirst {
|
||||
t.Fatal("expected patch_first gate")
|
||||
}
|
||||
if !gate.SkipDeploy || !gate.SkipMining {
|
||||
t.Fatalf("patch_first should block deploy+mining: %+v", gate)
|
||||
}
|
||||
if gate.Reason == "" {
|
||||
t.Fatal("expected gate reason")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateTripleOnionGatesPatchFirstDisabled(t *testing.T) {
|
||||
policy := DefaultTripleOnionPolicy()
|
||||
policy.PatchFirst = false
|
||||
recon := ReconSnapshot{CriticalExposed: 2, RiskScore: 50}
|
||||
|
||||
gate := EvaluateTripleOnionGates(policy, recon)
|
||||
if gate.SkipDeploy || gate.SkipMining {
|
||||
t.Fatalf("patch_first off should not block chains: %+v", gate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateTripleOnionGatesSkipMiningHighRisk(t *testing.T) {
|
||||
policy := DefaultTripleOnionPolicy()
|
||||
policy.PatchFirst = false
|
||||
policy.SkipMiningOnHighRisk = true
|
||||
policy.HighRiskThreshold = 40
|
||||
recon := ReconSnapshot{RiskScore: 55}
|
||||
|
||||
gate := EvaluateTripleOnionGates(policy, recon)
|
||||
if gate.SkipMining {
|
||||
if gate.SkipDeploy {
|
||||
t.Fatal("high risk should only skip mining, not deploy")
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("expected skip mining at risk 55 >= 40: %+v", gate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateTripleOnionGatesHighRiskBelowThreshold(t *testing.T) {
|
||||
policy := DefaultTripleOnionPolicy()
|
||||
policy.PatchFirst = false
|
||||
policy.SkipMiningOnHighRisk = true
|
||||
policy.HighRiskThreshold = 60
|
||||
recon := ReconSnapshot{RiskScore: 45}
|
||||
|
||||
gate := EvaluateTripleOnionGates(policy, recon)
|
||||
if gate.SkipMining {
|
||||
t.Fatalf("risk 45 < 60 should not skip mining: %+v", gate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateTripleOnionGatesPatchFirstOverridesHighRisk(t *testing.T) {
|
||||
policy := DefaultTripleOnionPolicy()
|
||||
policy.SkipMiningOnHighRisk = true
|
||||
recon := ReconSnapshot{CriticalExposed: 1, RiskScore: 90}
|
||||
|
||||
gate := EvaluateTripleOnionGates(policy, recon)
|
||||
if !gate.SkipDeploy || !gate.SkipMining {
|
||||
t.Fatalf("critical CVE should block both chains: %+v", gate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyIsolatedMiningPolicySkipsHostPaths(t *testing.T) {
|
||||
out := ApplyIsolatedMiningPolicy(DefaultMiningTierPolicy())
|
||||
if len(out.TierOrder) == 0 {
|
||||
t.Fatal("expected tier order")
|
||||
}
|
||||
if out.TierOrder[0] != TierDockerLoad {
|
||||
t.Fatalf("isolated policy should start docker_load, got %v", out.TierOrder)
|
||||
}
|
||||
skip := make(map[LOTLTier]bool, len(out.SkipTiers))
|
||||
for _, t := range out.SkipTiers {
|
||||
skip[t] = true
|
||||
}
|
||||
for _, hostTier := range []LOTLTier{TierExeSubprocess, TierCPUInprocess, TierPSInMemory, TierDotnet} {
|
||||
if !skip[hostTier] {
|
||||
t.Fatalf("mine_isolated_tier should skip %s", hostTier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyEnvTripleOnionOverrides(t *testing.T) {
|
||||
t.Setenv("AETHERFORGE_PATCH_FIRST", "false")
|
||||
t.Setenv("AETHERFORGE_SKIP_MINING", "true")
|
||||
t.Setenv("AETHERFORGE_MINE_ISOLATED", "1")
|
||||
t.Setenv("AETHERFORGE_HIGH_RISK_THRESHOLD", "75")
|
||||
|
||||
p := ApplyEnvTripleOnionOverrides(DefaultTripleOnionPolicy())
|
||||
if p.PatchFirst {
|
||||
t.Fatal("env should disable patch_first")
|
||||
}
|
||||
if !p.SkipMiningOnHighRisk {
|
||||
t.Fatal("env should enable skip mining")
|
||||
}
|
||||
if !p.MineIsolatedTier {
|
||||
t.Fatal("env should enable mine_isolated")
|
||||
}
|
||||
if p.HighRiskThreshold != 75 {
|
||||
t.Fatalf("threshold=%d want 75", p.HighRiskThreshold)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTripleOnionOrchestratorRunGatesMining(t *testing.T) {
|
||||
policy := DefaultTripleOnionPolicy()
|
||||
policy.PatchFirst = false
|
||||
policy.SkipMiningOnHighRisk = true
|
||||
policy.HighRiskThreshold = 10
|
||||
|
||||
var miningStarted bool
|
||||
o := NewTripleOnionOrchestrator(testCfg(config.BuiltinConfig{}), policy, TripleOnionHooks{
|
||||
RunReconTier: func(_ context.Context, tier string) ReconTierResult {
|
||||
if tier == "kev_scan" {
|
||||
return ReconTierResult{
|
||||
OK: true,
|
||||
Snapshot: ReconSnapshot{
|
||||
RiskScore: 80,
|
||||
CriticalExposed: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
return ReconTierResult{OK: true}
|
||||
},
|
||||
RunDeployLane: func(_ context.Context, _ string) (bool, string) {
|
||||
return false, "no targets"
|
||||
},
|
||||
RunMining: func(_ context.Context) {
|
||||
miningStarted = true
|
||||
},
|
||||
})
|
||||
|
||||
report := o.Run(context.Background())
|
||||
if miningStarted {
|
||||
t.Fatal("high risk gate should skip mining")
|
||||
}
|
||||
if !report.Gate.SkipMining {
|
||||
t.Fatalf("report gate should skip mining: %+v", report.Gate)
|
||||
}
|
||||
var sawMiningGate bool
|
||||
for _, a := range report.Attempts {
|
||||
if a.Phase == string(OnionPhaseMining) && a.Tier == "policy_gate" {
|
||||
sawMiningGate = true
|
||||
}
|
||||
}
|
||||
if !sawMiningGate {
|
||||
t.Fatal("expected gated mining attempt recorded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTripleOnionOrchestratorDeployStopsOnSuccess(t *testing.T) {
|
||||
var deployCalls int
|
||||
policy := DefaultTripleOnionPolicy()
|
||||
policy.DeployLanes = []string{"docker", "wsl"}
|
||||
o := NewTripleOnionOrchestrator(testCfg(config.BuiltinConfig{}), policy, TripleOnionHooks{
|
||||
RunReconTier: func(_ context.Context, _ string) ReconTierResult {
|
||||
return ReconTierResult{OK: true}
|
||||
},
|
||||
RunDeployLane: func(_ context.Context, lane string) (bool, string) {
|
||||
deployCalls++
|
||||
if lane == "docker" {
|
||||
return true, "container runtime ready"
|
||||
}
|
||||
return false, "skipped"
|
||||
},
|
||||
RunMining: func(_ context.Context) {},
|
||||
})
|
||||
|
||||
o.Run(context.Background())
|
||||
if deployCalls != 1 {
|
||||
t.Fatalf("deploy should stop after first success, calls=%d", deployCalls)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user