mod common;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use chrono::{Duration, Utc};
use common::*;
use holger_intel::feeds::FeedSpec;
use holger_intel::net::{Fetch, IntelNet, NetError};
use holger_intel::resolve::{FINDING_EXPIRED, FINDING_MISSING, FINDING_STALE};
use holger_intel::{
import, resolve, resolve_local, ImportRequest, IntelScanner, LiveIntel, LocalIntelDb, Staleness, StalenessRule,
};
use holger_update_bundle::digest::sha256_hex;
use modgunn::core::{ArtifactRef, Decision, Severity};
use modgunn::scan::{Artifact, Policy, Provenance, ProvenancePolicy};
fn scan_policy() -> Policy {
Policy { version: "intel-test".into(), provenance: ProvenancePolicy::Ignore, ..Policy::default() }
}
fn wheel_artifact(dir: &std::path::Path) -> Artifact {
let bytes = std::fs::read(fixture_wheel(dir)).unwrap();
let (name, version) = wheel_coords(&bytes);
Artifact::new(
ArtifactRef { ecosystem: "pip".into(), name, version, blob_sha256: sha256_hex(&bytes) },
Some("Apache-2.0".into()),
Provenance::unsigned(),
)
}
fn import_at(pki: &Pki, db: &LocalIntelDb, bundle: &std::path::Path, first: bool) -> anyhow::Result<holger_intel::ImportOutcome> {
let req = ImportRequest { bundle, policy: &pki.policy, db, first_import: first };
import(&req, &Fixed(Utc::now()), &MemAudit::default())
}
#[test]
fn feeds_normalize_into_one_enriched_set() {
let dir = tempfile::tempdir().unwrap();
let set = fixture_set(dir.path(), Utc::now());
assert_eq!(set.sources.len(), 3, "osv, nvd and kev are each recorded: {:?}", set.sources);
let req = set.advisories.iter().find(|a| a.package == "requests").expect("the OSV record");
assert!(req.aliases.iter().any(|a| a == "CVE-2018-18074"), "{req:?}");
assert!(req.kev, "KEV enriches by alias");
assert!(req.cvss.starts_with("CVSS:3.1/"), "NVD enriches by alias: {req:?}");
assert!(req.cwe.contains(&"CWE-522".to_string()));
assert_eq!(req.severity, Severity::High, "NVD's stronger severity is taken");
let again = fixture_set(dir.path(), set.generated_at);
let json = |s: &holger_intel::AdvisorySet| serde_json::to_vec(&s.advisories).unwrap();
assert_eq!(json(&set), json(&again), "the same feeds give the same advisory bytes");
}
#[test]
fn build_then_import_then_scan_finds_the_known_cve_in_a_fixture_wheel() {
let dir = tempfile::tempdir().unwrap();
let pki = Pki::new();
let built = bundle(&pki, dir.path(), 42, Utc::now());
assert!(built.report.approvals.len() == 2 && built.advisories >= 2);
let db = LocalIntelDb::open(dir.path().join("intel-db"));
let out = import_at(&pki, &db, &built.path, true).expect("first import");
assert_eq!(out.record.outcome, "new");
assert_eq!(out.installed.sequence, 42);
assert!(out.stored_bundle.exists() && out.receipt.exists());
let resolved = resolve(&db, StalenessRule::default(), &IntelNet::offline(), &[], Utc::now()).unwrap();
assert_eq!(resolved.evidence.source, "local-intel");
assert_eq!(resolved.evidence.sequence, Some(42));
let date = Utc::now().format("%Y-%m-%d").to_string();
assert_eq!(resolved.evidence.snapshot_date.as_deref(), Some(date.as_str()));
let scanner = IntelScanner::new(scan_policy(), resolved);
let v = scanner.scan(&wheel_artifact(dir.path()));
let hit = v
.verdict
.findings
.iter()
.find(|f| f.id == "PYSEC-2018-28")
.unwrap_or_else(|| panic!("the CVE is found: {:?}", v.verdict));
assert_eq!(hit.severity, Severity::Critical, "a KEV hit escalates to Critical");
assert_eq!(v.verdict.decision, Decision::Block);
assert!(
v.verdict.advisory_db_rev.contains("seq 42") && v.verdict.advisory_db_rev.contains(&date),
"the evidence names the bundle sequence and date: {}",
v.verdict.advisory_db_rev
);
assert!(v.evidence.describe().contains("offline"), "{}", v.evidence.describe());
}
#[test]
fn a_tampered_bundle_is_refused_and_writes_nothing() {
let dir = tempfile::tempdir().unwrap();
let pki = Pki::new();
let built = bundle(&pki, dir.path(), 7, Utc::now());
let mut members = holger_update_bundle::load_bundle(&built.path).unwrap();
let key = format!("payload/{}", holger_intel::ADVISORIES_REL);
let mut set = holger_intel::AdvisorySet::from_json_bytes(&members[&key]).unwrap();
set.advisories.retain(|a| a.package != "requests");
members.insert(key, set.to_json_bytes().unwrap());
let tampered = dir.path().join("tampered.holger.znippy");
holger_update_bundle::pack_bundle(&members, &tampered).unwrap();
let db = LocalIntelDb::open(dir.path().join("intel-db"));
let err = import_at(&pki, &db, &tampered, true).expect_err("a tampered bundle must be refused");
assert!(format!("{err:#}").contains("verification failed") || format!("{err:#}").contains("intel"), "{err:#}");
assert!(db.installed().unwrap().is_none(), "nothing went live");
let stranger = Pki::new();
let foreign = bundle(&stranger, dir.path(), 8, Utc::now());
let err = import_at(&pki, &db, &foreign.path, true).expect_err("an untrusted signer must be refused");
assert!(format!("{err:#}").contains("verification failed"), "{err:#}");
assert!(db.installed().unwrap().is_none());
}
#[test]
fn an_older_sequence_is_refused_and_the_newer_set_stays_live() {
let dir = tempfile::tempdir().unwrap();
let pki = Pki::new();
let older = bundle(&pki, dir.path(), 10, Utc::now() - Duration::days(1));
let newer = bundle(&pki, dir.path(), 11, Utc::now());
let db = LocalIntelDb::open(dir.path().join("intel-db"));
let err = import_at(&pki, &db, &newer.path, false).expect_err("no floor without first_import");
assert!(format!("{err:#}").to_lowercase().contains("floor"), "{err:#}");
import_at(&pki, &db, &newer.path, true).expect("first import");
let err = import_at(&pki, &db, &older.path, false).expect_err("rollback must be refused");
assert!(format!("{err:#}").contains("10") && format!("{err:#}").contains("11"), "{err:#}");
import_at(&pki, &db, &older.path, true).expect_err("first_import never bypasses a floor");
assert_eq!(db.installed().unwrap().unwrap().sequence, 11);
let again = import_at(&pki, &db, &newer.path, false).unwrap();
assert_eq!(again.record.outcome, "idempotent-reimport");
}
#[test]
fn an_audit_failure_refuses_the_import_and_changes_nothing() {
let dir = tempfile::tempdir().unwrap();
let pki = Pki::new();
let first = bundle(&pki, dir.path(), 20, Utc::now());
let second = bundle(&pki, dir.path(), 21, Utc::now());
let db = LocalIntelDb::open(dir.path().join("intel-db"));
fn req<'a>(b: &'a std::path::Path, pki: &'a Pki, db: &'a LocalIntelDb, first: bool) -> ImportRequest<'a> {
ImportRequest { bundle: b, policy: &pki.policy, db, first_import: first }
}
let err = import(&req(&first.path, &pki, &db, true), &Fixed(Utc::now()), &OffAudit).expect_err("audit off");
assert!(format!("{err:#}").contains("audit"), "{err:#}");
assert!(db.installed().unwrap().is_none());
assert!(!db.state_dir().exists() || std::fs::read_dir(db.state_dir()).unwrap().next().is_none(), "no floor");
import(&req(&first.path, &pki, &db, true), &Fixed(Utc::now()), &MemAudit::default()).unwrap();
let before = std::fs::read(db.root().join("installed.json")).unwrap();
let err = import(&req(&second.path, &pki, &db, false), &Fixed(Utc::now()), &FailingAudit).expect_err("audit write fails");
assert!(format!("{err:#}").contains("audit record could not be written"), "{err:#}");
assert_eq!(std::fs::read(db.root().join("installed.json")).unwrap(), before, "the live set is unchanged");
assert!(!db.root().join(LocalIntelDb::generation_file(21)).exists(), "the staged generation is undone");
assert!(!db.bundles_dir().join(format!("{}.holger.znippy", second.bundle_id)).exists());
let ok = import(&req(&second.path, &pki, &db, false), &Fixed(Utc::now()), &MemAudit::default()).unwrap();
assert_eq!(ok.record.outcome, "new");
}
struct Tripwire(Arc<AtomicU64>);
impl Fetch for Tripwire {
fn get(&self, url: &str) -> Result<Vec<u8>, NetError> {
self.0.fetch_add(1, Ordering::SeqCst);
Err(NetError::Unavailable { url: url.into(), why: "tripwire".into() })
}
}
#[test]
fn offline_mode_refuses_the_network() {
let dir = tempfile::tempdir().unwrap();
let pki = Pki::new();
let built = bundle(&pki, dir.path(), 5, Utc::now());
let db = LocalIntelDb::open(dir.path().join("intel-db"));
import_at(&pki, &db, &built.path, true).unwrap();
let feeds = FeedSpec::default_set(&["PyPI".to_string()], 2026);
let offline = IntelNet::offline();
let err = offline.get("https://osv-vulnerabilities.storage.googleapis.com/PyPI/all.zip").unwrap_err();
assert!(err.is_refused(), "{err}");
assert!(err.to_string().contains("network refused"));
let r = resolve(&db, StalenessRule::default(), &offline, &feeds, Utc::now()).unwrap();
assert_eq!(r.evidence.source, "local-intel");
assert_eq!(offline.refused_count(), 1, "only the explicit call above reached the gate");
let calls = Arc::new(AtomicU64::new(0));
let online = IntelNet::online(Box::new(Tripwire(calls.clone())));
let r = resolve(&db, StalenessRule::default(), &online, &feeds, Utc::now()).unwrap();
assert_eq!(calls.load(Ordering::SeqCst), 1, "online mode does try the network");
assert_eq!(r.evidence.source, "local-intel");
assert!(r.evidence.network.as_deref().unwrap_or("").contains("no network"), "{:?}", r.evidence);
}
#[test]
fn a_stale_database_warns_then_blocks() {
let dir = tempfile::tempdir().unwrap();
let pki = Pki::new();
let snapshot = Utc::now() - Duration::days(2);
let built = bundle(&pki, dir.path(), 30, snapshot);
let db = LocalIntelDb::open(dir.path().join("intel-db"));
import_at(&pki, &db, &built.path, true).unwrap();
let rule = StalenessRule { warn_after_days: 7, block_after_days: 30 };
rule.validate().unwrap();
assert!(StalenessRule { warn_after_days: 30, block_after_days: 7 }.validate().is_err());
let clean = Artifact::new(
ArtifactRef { ecosystem: "pip".into(), name: "requests".into(), version: "2.31.0".into(), blob_sha256: "00".into() },
None,
Provenance::unsigned(),
);
let at = |days: i64| {
let r = resolve_local(&db, rule, snapshot + Duration::days(days), None).unwrap();
IntelScanner::new(scan_policy(), r).scan(&clean)
};
let fresh = at(3);
assert_eq!(fresh.evidence.staleness, Staleness::Fresh);
assert_eq!(fresh.verdict.decision, Decision::Pass, "{:?}", fresh.verdict);
let warn = at(8);
assert_eq!(warn.evidence.staleness, Staleness::Warn);
assert_eq!(warn.verdict.decision, Decision::Warn);
let f = warn.verdict.findings.iter().find(|f| f.id == FINDING_STALE).expect("a stale finding");
assert!(f.summary.contains("seq 30") && f.summary.contains("8 days"), "{}", f.summary);
let block = at(31);
assert_eq!(block.evidence.staleness, Staleness::Block);
assert_eq!(block.verdict.decision, Decision::Block);
assert!(block.verdict.findings.iter().any(|f| f.id == FINDING_EXPIRED));
let empty = LocalIntelDb::open(dir.path().join("empty"));
let r = resolve_local(&empty, rule, Utc::now(), None).unwrap();
let v = IntelScanner::new(scan_policy(), r).scan(&clean);
assert_eq!(v.verdict.decision, Decision::Block);
assert!(v.verdict.findings.iter().any(|f| f.id == FINDING_MISSING));
}
#[test]
fn a_database_edited_after_import_is_refused() {
let dir = tempfile::tempdir().unwrap();
let pki = Pki::new();
let built = bundle(&pki, dir.path(), 3, Utc::now());
let db = LocalIntelDb::open(dir.path().join("intel-db"));
import_at(&pki, &db, &built.path, true).unwrap();
let live = db.root().join(LocalIntelDb::generation_file(3));
let mut bytes = std::fs::read(&live).unwrap();
bytes.extend_from_slice(b" ");
std::fs::write(&live, bytes).unwrap();
let err = db.load().expect_err("an edited database is refused");
assert!(format!("{err:#}").contains("does not match its install record"), "{err:#}");
}
#[test]
fn an_import_goes_live_without_a_restart() {
let dir = tempfile::tempdir().unwrap();
let pki = Pki::new();
let db = LocalIntelDb::open(dir.path().join("intel-db"));
let live = LiveIntel::new(db.clone(), StalenessRule::default());
let (_, e) = live.current(Utc::now()).unwrap();
assert_eq!(e.source, "none");
import_at(&pki, &db, &bundle(&pki, dir.path(), 1, Utc::now()).path, true).unwrap();
let (adb, e) = live.current(Utc::now()).unwrap();
assert_eq!(e.sequence, Some(1));
assert!(!adb.advisories.is_empty());
import_at(&pki, &db, &bundle(&pki, dir.path(), 2, Utc::now()).path, false).unwrap();
let (_, e) = live.current(Utc::now()).unwrap();
assert_eq!(e.sequence, Some(2), "the second import is what the next scan reads");
}
#[test]
fn the_default_feeds_carry_the_image_distributions() {
let defaults = holger_intel::feeds::DEFAULT_ECOSYSTEMS;
for want in ["Wolfi", "Chainguard"] {
assert!(defaults.contains(&want), "{want} is not fetched by default: {defaults:?}");
}
let rec = std::fs::read_to_string(
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../server/lib/tests/fixtures/sandbox/osv-wolfi-openssl.json"),
)
.unwrap();
let advisories = holger_intel::modgunn::osv::parse_doc(&rec).unwrap();
assert!(advisories.iter().any(|a| a.ecosystem == "wolfi" && a.package == "openssl"), "{:?}", advisories.iter().map(|a| (&a.ecosystem, &a.package)).collect::<Vec<_>>());
}