#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Soundness {
MayOver,
MustUnder,
Exact,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PrecisionContract {
ZeroFalsePositive,
RecallDriven,
FalseNegativesAccepted,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PrimitiveSoundness {
pub op_id: &'static str,
pub soundness: Soundness,
pub sanitizer_filter: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DynamicPrimitiveSoundness {
pub op_id: String,
pub soundness: Soundness,
pub sanitizer_filter: bool,
}
impl PrimitiveSoundness {
#[must_use]
pub const fn new(op_id: &'static str, soundness: Soundness) -> Self {
Self {
op_id,
soundness,
sanitizer_filter: false,
}
}
#[must_use]
pub const fn with_sanitizer_filter(mut self) -> Self {
self.sanitizer_filter = true;
self
}
}
impl DynamicPrimitiveSoundness {
#[must_use]
pub fn new(op_id: impl Into<String>, soundness: Soundness) -> Self {
Self {
op_id: op_id.into(),
soundness,
sanitizer_filter: false,
}
}
#[must_use]
pub fn with_sanitizer_filter(mut self) -> Self {
self.sanitizer_filter = true;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SoundnessViolation {
pub op_id: &'static str,
pub soundness: Soundness,
pub contract: PrecisionContract,
pub fix: &'static str,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct DynamicSoundnessViolation {
pub op_id: String,
pub soundness: Soundness,
pub contract: PrecisionContract,
pub fix: &'static str,
}
impl Soundness {
#[must_use]
pub const fn join(self, other: Soundness) -> Soundness {
match (self, other) {
(Soundness::MayOver, _) | (_, Soundness::MayOver) => Soundness::MayOver,
(Soundness::MustUnder, Soundness::MustUnder) => Soundness::MustUnder,
(Soundness::MustUnder, Soundness::Exact) | (Soundness::Exact, Soundness::MustUnder) => {
Soundness::MustUnder
}
(Soundness::Exact, Soundness::Exact) => Soundness::Exact,
}
}
}
pub fn validate_pipeline(
contract: PrecisionContract,
primitives: &[PrimitiveSoundness],
) -> Result<Soundness, SoundnessViolation> {
let mut joined = Soundness::Exact;
for primitive in primitives {
validate_primitive(contract, *primitive)?;
joined = joined.join(primitive.soundness);
}
Ok(joined)
}
pub fn validate_dynamic_pipeline(
contract: PrecisionContract,
primitives: &[DynamicPrimitiveSoundness],
) -> Result<Soundness, DynamicSoundnessViolation> {
let mut joined = Soundness::Exact;
for primitive in primitives {
validate_dynamic_primitive(contract, primitive)?;
joined = joined.join(primitive.soundness);
}
Ok(joined)
}
pub fn validate_primitive(
contract: PrecisionContract,
primitive: PrimitiveSoundness,
) -> Result<(), SoundnessViolation> {
match violation_fix(contract, primitive.soundness, primitive.sanitizer_filter) {
None => Ok(()),
Some(fix) => Err(SoundnessViolation {
op_id: primitive.op_id,
soundness: primitive.soundness,
contract,
fix,
}),
}
}
pub fn validate_dynamic_primitive(
contract: PrecisionContract,
primitive: &DynamicPrimitiveSoundness,
) -> Result<(), DynamicSoundnessViolation> {
match violation_fix(contract, primitive.soundness, primitive.sanitizer_filter) {
None => Ok(()),
Some(fix) => Err(DynamicSoundnessViolation {
op_id: primitive.op_id.clone(),
soundness: primitive.soundness,
contract,
fix,
}),
}
}
fn violation_fix(
contract: PrecisionContract,
soundness: Soundness,
sanitizer_filter: bool,
) -> Option<&'static str> {
match (contract, soundness, sanitizer_filter) {
(PrecisionContract::ZeroFalsePositive, Soundness::Exact, _)
| (PrecisionContract::ZeroFalsePositive, Soundness::MayOver, true)
| (PrecisionContract::RecallDriven, Soundness::Exact | Soundness::MayOver, _)
| (PrecisionContract::FalseNegativesAccepted, _, _) => None,
(PrecisionContract::ZeroFalsePositive, Soundness::MayOver, false) => {
Some("Fix: add an explicit sanitizer filter or use only Exact primitives.")
}
(PrecisionContract::ZeroFalsePositive, Soundness::MustUnder, _) => {
Some("Fix: zero-FP pipelines require Exact primitives or filtered MayOver primitives.")
}
(PrecisionContract::RecallDriven, Soundness::MustUnder, _) => {
Some("Fix: recall-driven pipelines cannot include under-approximating primitives.")
}
}
}
pub trait SoundnessTagged {
fn soundness(&self) -> Soundness;
}
#[cfg(test)]
mod tests {
use super::{
validate_dynamic_pipeline, DynamicPrimitiveSoundness, PrecisionContract, Soundness,
};
#[test]
fn dynamic_pipeline_rejects_zero_false_positive_unfiltered_mayover() {
let error = validate_dynamic_pipeline(
PrecisionContract::ZeroFalsePositive,
&[DynamicPrimitiveSoundness::new(
"vyre-libs::security::flows_to",
Soundness::MayOver,
)],
)
.expect_err("unfiltered MayOver must not satisfy zero false positive contracts");
assert_eq!(error.op_id, "vyre-libs::security::flows_to");
assert_eq!(error.soundness, Soundness::MayOver);
assert_eq!(error.contract, PrecisionContract::ZeroFalsePositive);
assert!(error.fix.contains("explicit sanitizer filter"));
}
#[test]
fn dynamic_pipeline_accepts_zero_false_positive_filtered_mayover() {
let soundness = validate_dynamic_pipeline(
PrecisionContract::ZeroFalsePositive,
&[DynamicPrimitiveSoundness::new(
"vyre-libs::security::flows_to_with_sanitizer",
Soundness::MayOver,
)
.with_sanitizer_filter()],
)
.expect("filtered MayOver should satisfy zero false positive contracts");
assert_eq!(soundness, Soundness::MayOver);
}
}