ci: add built-in supply chain policy fallback

This commit is contained in:
2026-06-23 23:10:16 +04:00
parent 4c1edef21b
commit 0e76c2ed7c
3 changed files with 120 additions and 3 deletions
+118 -3
View File
@@ -37,6 +37,8 @@ const PART2_ROOT_ENV: &str = "FPARKAN_CORPUS_PART2_ROOT";
const CI_ACCEPTANCE_ROADMAP: &str = "fixtures/acceptance/stage_0_2_roadmap.md";
const CI_ACCEPTANCE_COVERAGE: &str = "fixtures/acceptance/coverage.tsv";
const CI_ACCEPTANCE_REPORT: &str = "target/fparkan/acceptance/stage-0-2-audit.json";
const APPROVED_REGISTRY_SOURCE: &str = "registry+https://github.com/rust-lang/crates.io-index";
const SUPPLY_CHAIN_BANNED_PACKAGES: &[&str] = &["native-tls", "openssl", "openssl-sys"];
fn main() {
let args = std::env::args().skip(1).collect::<Vec<_>>();
@@ -170,10 +172,18 @@ fn run_cargo_fmt_check() -> Result<(), String> {
}
fn run_cargo_deny() -> Result<(), String> {
let cargo = std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
let status = Command::new(cargo)
let cargo_deny = std::env::var_os("CARGO_DENY").unwrap_or_else(|| "cargo-deny".into());
let available = Command::new(&cargo_deny).arg("--version").status();
match available {
Ok(status) if status.success() => {}
Ok(_) | Err(_) => {
eprintln!("cargo-deny is unavailable; running built-in supply-chain policy fallback");
return run_builtin_supply_chain_policy(Path::new("."));
}
}
let status = Command::new(cargo_deny)
.args([
"deny",
"check",
"--workspace",
"--all-features",
@@ -191,6 +201,20 @@ fn run_cargo_deny() -> Result<(), String> {
}
}
fn run_builtin_supply_chain_policy(root: &Path) -> Result<(), String> {
let mut failures = Vec::new();
validate_workspace_license(root, &mut failures)?;
validate_lockfile_supply_chain(root, &mut failures)?;
if failures.is_empty() {
Ok(())
} else {
Err(format!(
"built-in supply-chain policy failed:\n{}",
failures.join("\n")
))
}
}
fn run_cargo_doc() -> Result<(), String> {
let cargo = std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
let status = Command::new(cargo)
@@ -530,6 +554,52 @@ fn validate_lockfile(root: &Path, failures: &mut Vec<String>) {
}
}
fn validate_lockfile_supply_chain(root: &Path, failures: &mut Vec<String>) -> Result<(), String> {
let lockfile = root.join("Cargo.lock");
let packages = read_lockfile_packages(&lockfile)?;
for package in packages {
if let Some(source) = package.source.as_deref() {
if source != APPROVED_REGISTRY_SOURCE {
failures.push(format!(
"{}: package {} {} uses unapproved source {source}",
lockfile.display(),
package.name,
package.version
));
}
}
if SUPPLY_CHAIN_BANNED_PACKAGES.contains(&package.name.as_str()) {
failures.push(format!(
"{}: package {} {} is banned by built-in supply-chain policy",
lockfile.display(),
package.name,
package.version
));
}
}
Ok(())
}
fn read_lockfile_packages(lockfile: &Path) -> Result<Vec<CargoLockPackage>, String> {
let text =
fs::read_to_string(lockfile).map_err(|err| format!("{}: {err}", lockfile.display()))?;
let parsed = toml::from_str::<CargoLock>(&text)
.map_err(|err| format!("{}: invalid Cargo.lock TOML: {err}", lockfile.display()))?;
Ok(parsed.package)
}
#[derive(Debug, Deserialize)]
struct CargoLock {
package: Vec<CargoLockPackage>,
}
#[derive(Debug, Deserialize)]
struct CargoLockPackage {
name: String,
version: String,
source: Option<String>,
}
fn validate_workspace_license(root: &Path, failures: &mut Vec<String>) -> Result<(), String> {
let manifest = root.join("Cargo.toml");
let license = fs::read_to_string(root.join("LICENSE.txt"))
@@ -1950,6 +2020,51 @@ fparkan-render-vulkan = { path = "../../adapters/fparkan-render-vulkan" }
);
}
#[test]
fn lockfile_supply_chain_rejects_unapproved_sources() -> Result<(), String> {
let root = temp_dir("lockfile-source");
fs::create_dir_all(&root).map_err(|err| err.to_string())?;
fs::write(
root.join("Cargo.lock"),
r#"
[[package]]
name = "external"
version = "1.0.0"
source = "git+https://example.invalid/repo"
"#,
)
.map_err(|err| err.to_string())?;
let mut failures = Vec::new();
validate_lockfile_supply_chain(&root, &mut failures)?;
assert_eq!(failures.len(), 1);
assert!(failures[0].contains("uses unapproved source"));
fs::remove_dir_all(root).map_err(|err| err.to_string())?;
Ok(())
}
#[test]
fn lockfile_supply_chain_rejects_banned_packages() -> Result<(), String> {
let root = temp_dir("lockfile-ban");
fs::create_dir_all(&root).map_err(|err| err.to_string())?;
fs::write(
root.join("Cargo.lock"),
format!(
"[[package]]\nname = \"openssl\"\nversion = \"0.10.0\"\nsource = \"{APPROVED_REGISTRY_SOURCE}\"\n"
),
)
.map_err(|err| err.to_string())?;
let mut failures = Vec::new();
validate_lockfile_supply_chain(&root, &mut failures)?;
assert_eq!(failures.len(), 1);
assert!(failures[0].contains("is banned"));
fs::remove_dir_all(root).map_err(|err| err.to_string())?;
Ok(())
}
#[test]
fn detects_forbidden_domain_dependencies() {
assert!(!is_forbidden_domain_dependency("fparkan-render-vulkan"));