ONNX
security
malware-detection
Vigil / source /pkg /bundle /risk.go
turentomer's picture
Publish self-contained Vigil distribution
d2507b5 verified
Raw
History Blame Contribute Delete
13 kB
package bundle
import "sort"
// This file is the host-agnostic CAPABILITY / RISK surfacing layer that AUGMENTS
// (never replaces) the existing malicious/benign bundle label. It is a pure
// taxonomy + presentation layer over res.Findings already extracted by the
// analyzers: NO AST, NO new detection, NO Severity changes.
//
// The core robustness property: capabilities are driven by HOST-AGNOSTIC
// findings (what a script CAN DO), not by the corpus's own defanged exfil host.
// exfil-host-reference (which matches attacker.example literally) is demoted to
// evidence_only NETWORK_EGRESS support and never acts as a capability driver, so
// swapping attacker.example for a novel host does NOT silence the exfil-capable
// combo (which fires via the host-agnostic exfil-env-to-network source+sink
// co-occurrence).
// CapabilityName identifies one host-agnostic thing a bundled script can do.
type CapabilityName string
const (
CapSecretAccess CapabilityName = "SECRET_ACCESS"
CapNetworkEgress CapabilityName = "NETWORK_EGRESS"
CapOpaqueExecution CapabilityName = "OPAQUE_EXECUTION"
CapDynamicFetchExec CapabilityName = "DYNAMIC_FETCH_EXEC"
CapCodeExecution CapabilityName = "CODE_EXECUTION"
CapRegistryTamper CapabilityName = "REGISTRY_TAMPER"
CapFSDestructive CapabilityName = "FS_DESTRUCTIVE"
CapObfuscation CapabilityName = "OBFUSCATION"
CapAntiAnalysis CapabilityName = "ANTI_ANALYSIS"
CapPersistence CapabilityName = "PERSISTENCE"
)
// RiskTier is the combos-only risk ranking surfaced alongside the back-compat
// label. REVIEW is pinned to the existing escalation decision; ELEVATED/INFO/
// CLEAN rank the non-escalating remainder by capability power.
type RiskTier string
const (
TierClean RiskTier = "CLEAN"
TierInfo RiskTier = "INFO"
TierElevated RiskTier = "ELEVATED"
TierReview RiskTier = "REVIEW"
)
// Evidence is a single finding-derived datum supporting a capability. It carries
// the originating sibling/member, line, signal, and human-readable detail so the
// capability card can show operators exactly where the behavior was observed.
type Evidence struct {
File string `json:"file"`
Line int `json:"line"`
Signal string `json:"signal"`
Detail string `json:"detail"`
}
// Capability is one host-agnostic capability present in a bundle, with its
// power class ("high"|"low") and the evidence that produced it.
type Capability struct {
Name string `json:"name"`
Power string `json:"power"`
Evidence []Evidence `json:"evidence"`
}
// BigNasty is one big-nasty capability COMBINATION that fired, with a
// plain-English explanation of why it is dangerous. The big_nasties list is
// always computed and reported: it explains WHY a REVIEW verdict fired.
type BigNasty struct {
Name string `json:"name"`
Why string `json:"why"`
}
// CapabilityProfile is the full host-agnostic risk surface for a bundle: the
// capabilities present, the big-nasty combos that fired, and the derived tier.
type CapabilityProfile struct {
Capabilities []Capability `json:"capabilities,omitempty"`
BigNasties []BigNasty `json:"big_nasties,omitempty"`
Tier RiskTier `json:"risk_tier"`
}
// signalCapabilityMap maps each emitted Finding.Signal to the host-agnostic
// capabilities it drives. exfil-host-reference is deliberately NOT here as a
// driver β€” it is host-specific (matches attacker.example) and lives in
// evidenceOnlySignals/evidenceDecorates, only decorating NETWORK_EGRESS.
var signalCapabilityMap = map[string][]CapabilityName{
"env-credential-access": {CapSecretAccess},
"exfil-env-to-network": {CapSecretAccess, CapNetworkEgress},
"remote-code-execution": {CapDynamicFetchExec},
"reverse-shell": {CapNetworkEgress, CapCodeExecution},
"scheduled-network-callback": {CapNetworkEgress, CapPersistence},
"persistence": {CapPersistence},
"eval-decoded-payload": {CapOpaqueExecution, CapObfuscation},
"ships-opaque-executable": {CapOpaqueExecution},
"opaque-bytecode": {CapOpaqueExecution},
"ships-compiled-bytecode": {CapOpaqueExecution},
"suspicious-native-symbols": {CapOpaqueExecution},
"opaque-archive": {CapOpaqueExecution},
"opaque-archive-member": {CapOpaqueExecution},
"opaque-image": {CapOpaqueExecution},
"opaque-notebook": {CapOpaqueExecution},
"compiled-source-mismatch": {CapOpaqueExecution},
"compiled-without-matching-source": {CapOpaqueExecution},
"archive-contains-executable": {CapOpaqueExecution, CapAntiAnalysis},
"delegates-to-bundled-script": {CapCodeExecution},
"delegates-to-data": {CapCodeExecution},
"delegates-to-image": {CapCodeExecution},
"symlinked-executable-reference": {CapCodeExecution},
"allowed-tools-nl-directive": {CapCodeExecution},
"registry-rewrite": {CapRegistryTamper},
"destructive-command": {CapFSDestructive},
"embedded-encoded-blob": {CapObfuscation},
"image-embedded-blob": {CapObfuscation},
"data-embedded-directive": {CapObfuscation},
"image-embedded-directive": {CapObfuscation},
"padding-evasion": {CapObfuscation},
"archive-path-traversal": {CapAntiAnalysis},
"archive-bomb-guard": {CapAntiAnalysis},
"archive-too-deep": {CapAntiAnalysis},
"archive-member-limit": {CapAntiAnalysis},
}
// evidenceOnlySignals contribute evidence (or scan-coverage notes) but never act
// as a capability driver. exfil-host-reference is host-specific and is treated
// as supporting evidence for NETWORK_EGRESS only; references-unscanned-filetype
// and analyzer-error are scan-limitation notes (no capability at all).
var evidenceOnlySignals = map[string]bool{
"exfil-host-reference": true, // host-specific (attacker.example); decorates NETWORK_EGRESS only
"references-unscanned-filetype": true, // scan-limitation note
"analyzer-error": true, // scan-limitation note
}
// evidenceDecorates maps a host-specific evidence_only signal to the capability
// its evidence attaches to. It NEVER creates the capability β€” the entry only
// appends evidence when that capability is already (or also) present-driven, or
// surfaces the host-specific marker under that capability's evidence list. The
// capability's PRESENCE for combo purposes is still governed by host-agnostic
// drivers in signalCapabilityMap.
var evidenceDecorates = map[string]CapabilityName{
"exfil-host-reference": CapNetworkEgress,
}
// highPowerCaps marks the capabilities whose mere presence (without a combo)
// warrants ELEVATED. Low-power capabilities (CODE_EXECUTION-of-readable-script,
// OBFUSCATION, ANTI_ANALYSIS) only reach INFO on their own.
var highPowerCaps = map[CapabilityName]bool{
CapSecretAccess: true,
CapNetworkEgress: true,
CapOpaqueExecution: true,
CapDynamicFetchExec: true,
CapRegistryTamper: true,
CapFSDestructive: true,
}
// BuildCapabilityProfile is a PURE function over the already-extracted findings.
// It does NO new detection and NO AST: it folds each Finding through the
// host-agnostic signal->capability map, attaches host-specific evidence to the
// capabilities it decorates, classifies power, and computes the big-nasty
// combos. The returned profile's Tier is left zero-valued (CLEAN's zero value is
// the empty string, not TierClean) and MUST be set by ComputeTier at the call
// site, which pins REVIEW to the existing escalation decision.
func BuildCapabilityProfile(findings []Finding) CapabilityProfile {
present := map[CapabilityName][]Evidence{}
signals := map[string]bool{}
for _, f := range findings {
signals[f.Signal] = true
ev := Evidence{File: f.File, Line: f.Line, Signal: f.Signal, Detail: f.Detail}
for _, c := range signalCapabilityMap[f.Signal] {
present[c] = append(present[c], ev)
}
// Host-specific evidence decorates an existing/also-driven capability but
// never drives it on its own β€” append only when the capability is already
// present (driven by a host-agnostic finding). This guarantees no
// capability can be conjured purely by the host-specific match.
if dec, ok := evidenceDecorates[f.Signal]; ok {
if _, driven := present[dec]; driven {
present[dec] = append(present[dec], ev)
}
}
}
caps := make([]Capability, 0, len(present))
for name, evs := range present {
power := "low"
if highPowerCaps[name] {
power = "high"
}
caps = append(caps, Capability{Name: string(name), Power: power, Evidence: evs})
}
sortCaps(caps)
return CapabilityProfile{
Capabilities: caps,
BigNasties: detectBigNasties(present, signals),
}
}
// sortCaps orders capabilities deterministically by name so JSON/SARIF output
// and test assertions are stable across runs (map iteration is unordered).
func sortCaps(caps []Capability) {
sort.Slice(caps, func(i, j int) bool {
return caps[i].Name < caps[j].Name
})
}
// detectBigNasties computes the host-agnostic capability COMBINATIONS that drive
// the "why" behind a REVIEW. The combos are a documented SUPERSET of
// bundleEscalates's behavioral triggers so the plain-English explanation never
// contradicts the verdict. Critically, exfil-capable fires on the fused
// exfil-env-to-network finding (SECRET_ACCESS+NETWORK_EGRESS) OR on
// env-credential-access co-occurring with any NETWORK_EGRESS-driving sink β€” it
// NEVER requires exfil-host-reference, so it survives a host swap.
func detectBigNasties(present map[CapabilityName][]Evidence, signals map[string]bool) []BigNasty {
has := func(c CapabilityName) bool { _, ok := present[c]; return ok }
var out []BigNasty
if signals["reverse-shell"] {
out = append(out, BigNasty{
Name: "remote-shell",
Why: "Opens a reverse/bind shell β€” hands an attacker an interactive shell on the host, independent of any destination string.",
})
}
if signals["scheduled-network-callback"] {
out = append(out, BigNasty{
Name: "scheduled-phone-home",
Why: "Installs a persistent/scheduled job that performs network I/O β€” a recurring callout that survives restarts.",
})
}
if has(CapSecretAccess) && has(CapNetworkEgress) {
out = append(out, BigNasty{
Name: "exfil-capable",
Why: "Reads a credential AND opens a network sink β€” can steal secrets and ship them off-box, regardless of destination host.",
})
}
if has(CapOpaqueExecution) {
out = append(out, BigNasty{
Name: "runs-uninspectable-code",
Why: "Executes code that cannot be read (compiled/opaque artifact or source-mismatched binary, the xz pattern).",
})
}
if has(CapDynamicFetchExec) {
out = append(out, BigNasty{
Name: "fetch-and-run",
Why: "Downloads remote code and runs it immediately β€” payload is whatever the server returns at runtime.",
})
}
if has(CapObfuscation) && (has(CapNetworkEgress) || has(CapCodeExecution) || has(CapOpaqueExecution)) {
out = append(out, BigNasty{
Name: "hidden-and-active",
Why: "Hidden/encoded content combined with an active execution or egress capability β€” concealing live behavior.",
})
}
if has(CapRegistryTamper) {
out = append(out, BigNasty{
Name: "external-registry-redirect",
Why: "Rewrites package/registry config to redirect installs to an external endpoint β€” supply-chain redirect.",
})
}
if has(CapFSDestructive) {
out = append(out, BigNasty{
Name: "data-destruction",
Why: "Issues mass-destructive filesystem commands capable of wiping user data.",
})
}
return out
}
// ComputeTier ranks a bundle into a RiskTier. REVIEW is PINNED to the existing
// label decision (escalatedOrMalicious := scorerMalicious || bundleEscalates) so
// the derived tier β€” and therefore the derived label β€” can NEVER diverge from
// today's bundle label on the corpus. The combo taxonomy does NOT gate REVIEW;
// it only ranks the NON-escalating remainder into ELEVATED/INFO/CLEAN by the
// power of the host-agnostic capabilities present.
func ComputeTier(escalatedOrMalicious bool, prof CapabilityProfile) RiskTier {
if escalatedOrMalicious {
return TierReview
}
high := false
low := false
for _, c := range prof.Capabilities {
if c.Power == "high" {
high = true
} else {
low = true
}
}
switch {
case high:
return TierElevated
case low:
return TierInfo
default:
return TierClean
}
}
// DeriveLabel keeps the back-compat bundle label byte-identical to today's:
// malicious iff REVIEW. Since REVIEW is pinned to (scorerMalicious ||
// bundleEscalates), this equals the exact OR that MakeBundleVerdict uses to set
// the label today, guaranteeing ZERO regression on the 240-bundle eval.
func DeriveLabel(tier RiskTier) string {
if tier == TierReview {
return "malicious"
}
return "benign"
}