Files
NeuroSploit/neurosploit-rs/crates/harness/src/grounding.rs
T
CyberSecurityUP 2e25809a93 v3.5.5 — cloud infrastructure testing + REPL polish
Cloud testing:
- +17 cloud agents (agents_md/infra/) for AWS/GCP/Azure: IAM/RBAC privesc,
  storage exposure (S3/GCS/Blob), compute & network exposure + IMDS, secrets
  (Secrets Manager / Secret Manager / Key Vault), SA/SP key abuse, Entra ID
  enum, and a multi-cloud footprint/identity recon agent. Library 348 -> 365.
- creds.yaml gains aws:/gcp:/azure: blocks (Creds::cloud). The harness exports
  provider env vars (AWS_*, GOOGLE_APPLICATION_CREDENTIALS, AZURE_* SP) so
  aws/gcloud/az authenticate automatically, and injects a cloud directive. GCP
  inline JSON is written to a temp file. Best-practice auth per provider.

REPL polish:
- /chain <n> (attack-chain depth, wired to Session.chain_depth), /agents list
  (library category counts incl. infra/cloud); /show now shows chain-depth and
  enabled integrations. Tab-completion + help updated.

Docs: README badges (365 agents / 14 providers), new "Cloud credentials" section;
RELEASE notes. Version 3.5.4 -> 3.5.5.
2026-07-01 22:38:27 -03:00

88 lines
3.7 KiB
Rust

//! Verification / grounding engine (v3.5.5).
//!
//! Hard rule: **no claim enters the world model without a tool receipt** — raw
//! tool output, not the LLM's paraphrase. This is the empirical anti-hallucination
//! anchor that complements the POMDP belief gate:
//!
//! - **Black-box**: grounding is empirical — the finding's evidence must look
//! like raw tool output (an HTTP response, an OOB callback, an error oracle),
//! not prose.
//! - **White-box**: grounding is symbolic — a file:line reference into the
//! reviewed source (reachability/taint), checked against the collected context.
//!
//! Ungrounded claims are flagged (`receipt_missing`) so the reward layer can
//! penalize them (the "claim without receipt" term).
use crate::types::Finding;
/// Verdict of grounding a single finding.
pub struct Grounded {
pub ok: bool,
pub kind: &'static str, // "empirical" | "symbolic" | "missing"
pub reason: String,
}
/// Markers that suggest the evidence is a real tool receipt rather than prose.
fn looks_empirical(evidence: &str) -> bool {
let e = evidence.to_lowercase();
let markers = [
"http/", "status", "200", "301", "302", "401", "403", "500",
"set-cookie", "location:", "content-type", "<html", "<script",
"server:", "x-", "alert(", "uid=", "root:", "sql", "error", "stack",
"callback", "oob", "collaborator", "$ ", "# ", "curl", "nmap",
];
evidence.len() >= 24 && markers.iter().filter(|m| e.contains(*m)).count() >= 2
}
/// White-box: evidence should reference a source location present in `context`.
fn looks_symbolic(f: &Finding, context: &str) -> bool {
// endpoint like file.ext:line, and the file appears in the reviewed source.
let loc = &f.endpoint;
if let Some((file, _)) = loc.rsplit_once(':') {
let base = file.rsplit('/').next().unwrap_or(file);
if !base.is_empty() && context.contains(base) {
return true;
}
}
// or the evidence quotes code that is actually in the context
!f.evidence.trim().is_empty()
&& f.evidence.split_whitespace().take(6).collect::<Vec<_>>().join(" ")
.split_whitespace()
.filter(|t| t.len() > 4 && context.contains(*t))
.count()
>= 2
}
/// Ground a finding. `context` is the reviewed source for white-box (empty for
/// black-box). Returns whether it has a valid receipt and of what kind.
pub fn ground(f: &Finding, context: &str, whitebox: bool) -> Grounded {
if whitebox && !context.is_empty() {
if looks_symbolic(f, context) {
return Grounded { ok: true, kind: "symbolic", reason: "source location/quote matches reviewed code".into() };
}
return Grounded { ok: false, kind: "missing", reason: "no source reference into reviewed code".into() };
}
if looks_empirical(&f.evidence) {
Grounded { ok: true, kind: "empirical", reason: "evidence resembles raw tool output".into() }
} else {
Grounded { ok: false, kind: "missing", reason: "evidence is paraphrase, not a tool receipt".into() }
}
}
/// Apply the grounding gate to a finding set. Ungrounded findings are flagged
/// (receipt recorded in `votes`) and demoted to unvalidated so they never get
/// reported as confirmed. Returns (kept, demoted_count).
pub fn gate(mut findings: Vec<Finding>, context: &str, whitebox: bool) -> (Vec<Finding>, usize) {
let mut demoted = 0;
for f in findings.iter_mut() {
let g = ground(f, context, whitebox);
if !g.ok {
f.validated = false;
f.votes = format!("{} · receipt_missing", f.votes);
demoted += 1;
}
}
findings.retain(|f| f.validated);
(findings, demoted)
}