Files
NeuroSploit/neurosploit-rs/crates/harness/src/belief.rs
T
CyberSecurityUP 797a8eb7a1 v3.6.5: LLM red-teaming (jailbreaks & prompt injection) + Opus 5 / Sonnet 5 / Kimi K3
- Add 12 technique/scenario LLM red-team agents (AI category 18 → 30, total 429):
  jailbreaks — AdvPrefix, PAIR, TAP, Crescendo, many-shot, persona/DAN,
  encoding/obfuscation, refusal-suppression; prompt-injection scenarios — direct,
  indirect (RAG/web/email/tool output), goal hijacking, tool/function-call abuse,
  system-prompt/secret exfiltration. Each runs an attacker→LLM-judge loop
  (baseline refusal → technique across variants → verdict), proving the bypass
  with a benign, redacted receipt. Generated by scripts/build_llm_redteam_v365.py.
- Add REDTEAM_DOCTRINE and inject it into run_ai so every AI test follows the
  baseline→technique→judge method across scenarios.
- Models: add Claude Opus 5 and Sonnet 5 (Anthropic) and a new Moonshot AI (Kimi)
  provider with Kimi K3/K2 (moonshot:kimi-k3, MOONSHOT_API_KEY) — 15 providers.
- Docs: README/TUTORIAL/RELEASE — new AI/LLM red-team engagement mode + section,
  model/env-key tables, agent-library counts (429), badges.

Also includes the v3.6.4 grounding fix (#33) landing on main.
2026-07-28 13:38:15 -03:00

147 lines
5.6 KiB
Rust

//! POMDP belief-state world model (v3.6.5).
//!
//! The target is only partially observable, so we don't track booleans — we
//! track a **belief**: a property graph whose nodes (host / service / vuln /
//! credential) each carry a probability that the proposition is true. Recon
//! produces *observations* that update those beliefs via a Bayesian step; the
//! per-node Shannon entropy measures how diffuse the belief still is.
//!
//! - **Black-box**: beliefs start uncertain (~0.5) and sharpen with observation.
//! - **White-box**: the world model is built (near-)deterministically from
//! source/SAST, so beliefs collapse toward 0/1 — the POMDP degenerates into an
//! MDP and uncertainty migrates to *path reachability*, not state.
//!
//! This is the substrate for value-of-information planning (see `pomdp.rs`): when
//! a node's belief is diffuse, gathering an observation about it is worth more
//! than acting on it — which is also the anti-hallucination criterion.
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// What a belief node is about.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Kind {
Host, // a host exists / is reachable
Service, // a service/endpoint is present
Vuln, // a specific weakness is present
Exploit, // the weakness is actually exploitable
Credential, // a credential is valid
}
/// A single proposition with a probability of being true and the evidence count
/// behind it (used for confidence/entropy).
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Node {
pub id: String,
pub kind: Kind,
pub label: String,
/// P(proposition is true) ∈ [0,1].
pub p: f64,
/// number of independent observations folded in.
pub obs: u32,
}
impl Node {
/// Shannon entropy in bits of the Bernoulli(p) belief — 1.0 = maximally
/// uncertain (p=0.5), 0.0 = certain.
pub fn entropy(&self) -> f64 {
let p = self.p.clamp(1e-6, 1.0 - 1e-6);
-(p * p.log2() + (1.0 - p) * (1.0 - p).log2())
}
}
/// A directed edge: "from enables/leads-to to" with a transition probability.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Edge {
pub from: String,
pub to: String,
pub p: f64,
}
/// The belief: a property graph over the partially-observed target.
#[derive(Default, Clone, Serialize, Deserialize)]
pub struct WorldModel {
pub nodes: HashMap<String, Node>,
pub edges: Vec<Edge>,
/// true once beliefs were built deterministically (white-box → MDP regime).
pub deterministic: bool,
}
/// A sensed observation about a node: P(observation | true) vs P(observation | false).
/// `positive` true means the observation supports the proposition.
pub struct Observation<'a> {
pub node: &'a str,
pub positive: bool,
/// sensor reliability ∈ (0.5, 1.0]; how much one observation moves the belief.
pub reliability: f64,
}
impl WorldModel {
pub fn new() -> Self {
WorldModel::default()
}
/// Seed a node with a prior. Black-box priors are ~0.5 (unknown); white-box
/// callers pass priors near 0/1.
pub fn add(&mut self, id: &str, kind: Kind, label: &str, prior: f64) {
self.nodes.entry(id.to_string()).or_insert_with(|| Node {
id: id.to_string(),
kind,
label: label.to_string(),
p: prior.clamp(0.0, 1.0),
obs: 0,
});
}
pub fn link(&mut self, from: &str, to: &str, p: f64) {
self.edges.push(Edge { from: from.into(), to: to.into(), p: p.clamp(0.0, 1.0) });
}
/// Bayesian update of a node's belief from one observation. With sensor
/// reliability r: a positive obs multiplies the odds by r/(1-r), a negative
/// one by (1-r)/r.
pub fn observe(&mut self, o: Observation) {
let r = o.reliability.clamp(0.5 + 1e-6, 1.0 - 1e-6);
if let Some(n) = self.nodes.get_mut(o.node) {
let p = n.p.clamp(1e-6, 1.0 - 1e-6);
let prior_odds = p / (1.0 - p);
let lr = if o.positive { r / (1.0 - r) } else { (1.0 - r) / r };
let post_odds = prior_odds * lr;
n.p = post_odds / (1.0 + post_odds);
n.obs += 1;
}
}
/// Collapse a node to (near-)certainty — used by white-box when SAST/dataflow
/// determines the proposition deterministically.
pub fn set_known(&mut self, id: &str, truth: bool) {
if let Some(n) = self.nodes.get_mut(id) {
n.p = if truth { 0.98 } else { 0.02 };
n.obs += 3;
}
}
/// Mean entropy across nodes of a kind (or all). 1.0 = totally diffuse.
pub fn uncertainty(&self, kind: Option<Kind>) -> f64 {
let rel: Vec<&Node> = self.nodes.values()
.filter(|n| kind.map(|k| n.kind == k).unwrap_or(true)).collect();
if rel.is_empty() {
return 1.0;
}
rel.iter().map(|n| n.entropy()).sum::<f64>() / rel.len() as f64
}
/// Nodes whose belief is still diffuse (entropy above `thresh`) — the recon
/// frontier: where collecting an observation has the highest value.
pub fn frontier(&self, thresh: f64) -> Vec<&Node> {
let mut v: Vec<&Node> = self.nodes.values().filter(|n| n.entropy() > thresh).collect();
v.sort_by(|a, b| b.entropy().partial_cmp(&a.entropy()).unwrap_or(std::cmp::Ordering::Equal));
v
}
/// Is a proposition confident enough to *act/assert* on? (low entropy + high p)
pub fn is_confident(&self, id: &str, min_p: f64, max_entropy: f64) -> bool {
self.nodes.get(id).map(|n| n.p >= min_p && n.entropy() <= max_entropy).unwrap_or(false)
}
}