xtask/tests/gix_advisory_floor.rs
//! **No committed lock resolves a gix with GHSA-q54r-jf2p-rfr2** (lane OSSF-SILVER-CODE,
//! row GIX-090, 2026-10-31).
//!
//! gix 0.88.0 up to 0.90.0 let an untrusted `.git/config` turn off the `.git` path
//! protection checks (`protect_options()` unfiltered). gunnar-cli's worktree checkout
//! reaches gix through gunnar-client and nornir-git, so the advisory was the last one
//! Scorecard counted for Gunnar. The test reads every committed `Cargo.lock` of the
//! repository (the guard fixtures excepted: they are bad on purpose), not the manifests:
//! a `^0.90` requirement over a stale lock still ships the old gix.
use std::path::{Path, PathBuf};
const FLOOR: (u64, u64, u64) = (0, 90, 0);
const ADVISORY: &str = "GHSA-q54r-jf2p-rfr2";
fn parse(v: &str) -> (u64, u64, u64) {
let core = v.split(['-', '+']).next().unwrap_or(v);
let mut it = core.split('.').map(|p| p.parse::<u64>().unwrap_or(0));
(
it.next().unwrap_or(0),
it.next().unwrap_or(0),
it.next().unwrap_or(0),
)
}
/// The versions of the package named exactly `name` in a lock.
fn versions_of(lock: &str, name: &str) -> Vec<String> {
let mut out = Vec::new();
let mut current: Option<String> = None;
for line in lock.lines() {
let l = line.trim();
if l == "[[package]]" {
current = None;
} else if let Some(v) = l.strip_prefix("name = ") {
current = Some(v.trim_matches('"').to_string());
} else if let Some(v) = l.strip_prefix("version = ") {
if current.as_deref() == Some(name) {
out.push(v.trim_matches('"').to_string());
}
}
}
out
}
fn locks(root: &Path) -> Vec<PathBuf> {
let mut out = Vec::new();
let mut stack = vec![root.to_path_buf()];
while let Some(d) = stack.pop() {
let Ok(rd) = std::fs::read_dir(&d) else {
continue;
};
for e in rd.flatten() {
let p = e.path();
let n = e.file_name().to_string_lossy().into_owned();
if p.is_dir() {
if !matches!(n.as_str(), "target" | ".git" | "fixtures" | "vendor") {
stack.push(p);
}
} else if n == "Cargo.lock" {
out.push(p);
}
}
}
out.sort();
out
}
#[test]
fn the_reader_sees_a_vulnerable_gix_and_only_gix() {
let lock = "[[package]]\nname = \"gix\"\nversion = \"0.88.0\"\n\n[[package]]\nname = \"gix-hash\"\nversion = \"0.27.0\"\n";
assert_eq!(versions_of(lock, "gix"), ["0.88.0"]);
assert!(parse("0.88.0") < FLOOR && parse("0.89.0") < FLOOR && parse("0.90.0") >= FLOOR);
}
#[test]
fn no_committed_lock_resolves_gix_below_0_90() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
let all = locks(&root);
assert!(
all.iter()
.any(|l| l.parent().and_then(|p| p.canonicalize().ok()) == root.canonicalize().ok()),
"the root lock was not found: {all:?}"
);
let mut bad = Vec::new();
for lock in &all {
let text = std::fs::read_to_string(lock).unwrap();
for v in versions_of(&text, "gix") {
if parse(&v) < FLOOR {
bad.push(format!(
"{}: gix {v} (< 0.90.0: {ADVISORY})",
lock.display()
));
}
}
}
assert!(bad.is_empty(), "{}", bad.join("\n"));
}