Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

server/lib/tests/cargo_publish_walks_the_intake_chain.rs

//! **`cargo publish` is a door of the intake chain** (lane HOL-CARGO-PUBLISH-DOOR, Holger 3.1.1).
//!
//! P19 made every repository outside the intake chain refuse writes where the policy requires a
//! scan, so `cargo publish` stopped working on a chain Holger. The repository
//! `promotion.intake.cargo` names (`ron_repo_type: "rust"`, no storage of its own) is now the
//! chain's cargo door, the way `promotion.intake.npm` is its npm door:
//!
//! * `cargo publish` (`PUT /<door>/api/v1/crates/new`) is INGESTED into `incoming` at the
//!   coordinate `<door>/<name>/<name>-<version>.crate`, and nothing of it is served yet;
//! * the walk to AVAILABLE is the chain's: SCANNED, POLICY_CHECKED, two approvers, SEALED;
//! * the sparse index lists only the AVAILABLE versions, its line read from the sealed crate's
//!   own `Cargo.toml` (`cksum` the sealed bytes' sha256), and `cargo install` installs from it;
//! * `cargo yank` and `cargo yank --undo` go through the door (audited, AVAILABLE versions only);
//! * a crate carrying a malware test file is refused at the publish, by the rule's name;
//! * a crate pinned to a dependency with a known CVE is refused at SCANNED, by the advisory's
//!   name and the dependency's.
//!
//! Every client step is the real `cargo` (it is on `PATH` in the toolchain container), offline:
//! the crates.io dependency of the CVE case resolves from a local directory source.

use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::process::Command;

use holger_server_lib::{read_ron_config, wire_holger, Holger};
use sha2::Digest as _;
use traits::test_support::hold_port;

fn token(who: &str) -> String {
    format!("{who}-token-of-sufficient-length-for-holger")
}

const EICAR_RULE: &str =
    "rule EICAR_Test_File { strings: $a = \"EICAR-STANDARD-ANTIVIRUS-TEST-FILE\" condition: $a }";

/// RUSTSEC-2021-0003 (CVE-2021-25900, smallvec `insert_many` buffer overflow), as OSV writes it.
const SMALLVEC_ADVISORY: &str = r#"{"id": "RUSTSEC-2021-0003", "modified": "2021-01-08T00:00:00Z",
  "aliases": ["CVE-2021-25900"], "summary": "Buffer overflow in SmallVec::insert_many",
  "database_specific": {"severity": "CRITICAL"},
  "affected": [ { "package": { "ecosystem": "crates.io", "name": "smallvec" },
    "ranges": [ { "type": "SEMVER", "events": [ { "introduced": "0.6.3" }, { "fixed": "0.6.14" },
      { "introduced": "1.0.0" }, { "fixed": "1.6.1" } ] } ] } ] }"#;

struct Running {
    _data: tempfile::TempDir,
    _cfg: tempfile::NamedTempFile,
    _holger: Holger,
    _rt: tokio::runtime::Runtime,
    http: u16,
}

fn install_intel(dir: &Path) {
    use holger_intel::chrono::Utc;
    use holger_intel::{AdvisorySet, InstalledIntel, LocalIntelDb};
    let advisories = holger_intel::feeds::parse_osv_feed(SMALLVEC_ADVISORY.as_bytes()).unwrap();
    let set = AdvisorySet::from_advisories(Utc::now(), vec![], &advisories);
    let n = set.advisories.len() as u64;
    let db = LocalIntelDb::open(dir);
    let bytes = set.to_json_bytes().unwrap();
    db.stage_generation(1, &bytes).unwrap();
    db.activate(&InstalledIntel {
        schema: holger_intel::db::INSTALLED_SCHEMA.into(),
        bundle_id: "holger-intel-1".into(),
        sequence: 1,
        supplier: holger_intel::DEFAULT_SUPPLIER.into(),
        snapshot_at: Utc::now(),
        imported_at: Utc::now(),
        candidate_digest: "00".into(),
        release_signer_fingerprint: "00".into(),
        advisories_file: LocalIntelDb::generation_file(1),
        advisories_sha256: holger_server_lib::security::blob_digest_hex(&bytes),
        advisories: n,
    })
    .unwrap();
}

fn start() -> Running {
    let mut last = String::new();
    for _ in 0..8 {
        match try_start(hold_port(), hold_port()) {
            Ok(r) => return r,
            Err(e) => last = e,
        }
    }
    panic!("server never started; last error: {last}");
}

fn try_start(grpc: u16, http: u16) -> Result<Running, String> {
    let data = tempfile::tempdir().unwrap();
    let d = data.path().display().to_string();
    let intel = data.path().join("intel");
    install_intel(&intel);
    let yara = data.path().join("yara");
    std::fs::create_dir_all(&yara).unwrap();
    std::fs::write(yara.join("eicar.yar"), EICAR_RULE).unwrap();
    let raw = |name: &str| {
        format!(
            r#"(ron_name: "{name}", ron_repo_type: "raw", ron_upstreams: [], ron_in: None,
         ron_out: Some((ron_storage_endpoint: "storage", ron_exposed_endpoint: "door")),
         ron_data_dir: Some("{d}/{name}")),"#
        )
    };
    let methods: Vec<String> = ["ida", "bob", "carol", "dev", "ivy", "adm"]
        .iter()
        .map(|p| {
            format!(
                r#"StaticToken(subject: "{p}", token: Some("{}"))"#,
                token(p)
            )
        })
        .collect();
    let ron = format!(
        r#"(
    repositories: [ {incoming} {quarantine} {approved} {production}
        (ron_name: "crates", ron_repo_type: "rust", ron_base_url: Some("http://127.0.0.1:{http}"), ron_upstreams: [], ron_in: None,
         ron_out: Some((ron_storage_endpoint: "storage", ron_exposed_endpoint: "door"))),
    ],
    exposed_endpoints: [ (ron_name: "door", ron_url: "127.0.0.1:{grpc}", ron_http_url: Some("127.0.0.1:{http}")) ],
    storage_endpoints: [ (ron_name: "storage", ron_storage_type: "znippy", ron_path: "{d}") ],
    audit: (dir: "{d}/audit", enabled: true),
    auth: Some((
        methods: [ {methods} ],
        roles: {{ "ida": Writer, "bob": Reader, "carol": Reader, "dev": Writer, "ivy": Writer, "adm": Admin }},
        duties: {{ "ida": [Importer], "bob": [SecurityApprover], "carol": [SecurityApprover], "dev": [Developer], "ivy": [Importer], "adm": [RepositoryAdmin] }},
    )),
    intel: Some((db_dir: "{intel}", offline: true)),
    promotion: Some((
        ladder_name: "intake",
        stages: [
            (name: "incoming", repo: "incoming"), (name: "quarantine", repo: "quarantine"),
            (name: "approved", repo: "approved"), (name: "production", repo: "production"),
        ],
        intake: Some((
            incoming: "incoming", quarantine: "quarantine", approved: "approved", production: "production",
            rules: (sandbox: off),
            approvals: 2,
            scan: Some((required: [vulnerability])),
            inspect: (yara: Some((rules: Some("{yara}"), required: true))),
            cargo: Some("crates"),
        )),
    )),
)"#,
        incoming = raw("incoming"),
        quarantine = raw("quarantine"),
        approved = raw("approved"),
        production = raw("production"),
        methods = methods.join(", "),
        intel = intel.display(),
        yara = yara.display(),
    );
    let mut cfg = tempfile::NamedTempFile::new().unwrap();
    cfg.write_all(ron.as_bytes()).unwrap();
    let mut holger: Holger = read_ron_config(cfg.path()).map_err(|e| format!("config: {e:#}"))?;
    holger
        .instantiate_backends()
        .map_err(|e| format!("backends: {e:#}"))?;
    wire_holger(&mut holger).map_err(|e| format!("wire: {e:#}"))?;
    let rt = tokio::runtime::Builder::new_multi_thread()
        .worker_threads(2)
        .enable_all()
        .build()
        .unwrap();
    {
        let _in = rt.enter();
        holger.start().map_err(|e| format!("start: {e:#}"))?;
    }
    let r = Running {
        _data: data,
        _cfg: cfg,
        _holger: holger,
        _rt: rt,
        http,
    };
    for _ in 0..200 {
        if request(http, "GET", "/healthz", Some("ida"), &[]).0 == 200 {
            return Ok(r);
        }
        std::thread::sleep(std::time::Duration::from_millis(20));
    }
    Err("never healthy".into())
}

/// One HTTP/1.1 request, as `who` when named: the status and the body.
fn request(port: u16, method: &str, path: &str, who: Option<&str>, body: &[u8]) -> (u16, String) {
    let mut c = std::net::TcpStream::connect(("127.0.0.1", port)).expect("connect");
    let auth = who
        .map(|w| format!("Authorization: Bearer {}\r\n", token(w)))
        .unwrap_or_default();
    let head = format!(
        "{method} {path} HTTP/1.1\r\nHost: 127.0.0.1\r\n{auth}Content-Length: {}\r\nConnection: close\r\n\r\n",
        body.len()
    );
    c.write_all(head.as_bytes()).unwrap();
    c.write_all(body).unwrap();
    let mut out = Vec::new();
    c.read_to_end(&mut out).unwrap();
    let text = String::from_utf8_lossy(&out).to_string();
    let status = text
        .split_whitespace()
        .nth(1)
        .and_then(|s| s.parse().ok())
        .unwrap_or(0);
    let body = text
        .split_once("\r\n\r\n")
        .map(|(_, b)| b.to_string())
        .unwrap_or_default();
    (status, body)
}

/// The chain's walk: whoever may take the next step takes it, until AVAILABLE. `Err` names the
/// state it was held at and every refusal.
fn walk(port: u16, coordinate: &str) -> Result<Vec<String>, String> {
    let mut walk = Vec::new();
    let mut state = String::from("INGESTED");
    'walk: for _ in 0..12 {
        if state == "AVAILABLE" {
            return Ok(walk);
        }
        let mut refusals = Vec::new();
        for who in ["ida", "bob", "carol"] {
            let (status, body) = request(
                port,
                "POST",
                &format!("/-/intake/advance?name={coordinate}&verb=advance"),
                Some(who),
                &[],
            );
            if status == 200 {
                let v: serde_json::Value = serde_json::from_str(&body).unwrap_or_default();
                state = v["state"].as_str().unwrap_or("?").to_string();
                walk.push(format!("{who}→{state}"));
                continue 'walk;
            }
            refusals.push(format!("{who}: {status} {body}"));
        }
        return Err(format!(
            "held at {state}: {}; walk so far: {walk:?}",
            refusals.join(" | ")
        ));
    }
    Err(format!("never AVAILABLE: {walk:?}"))
}

fn have_cargo() -> bool {
    Command::new("cargo")
        .arg("--version")
        .output()
        .map(|o| o.status.success())
        .unwrap_or(false)
}

fn write(path: &Path, body: &str) {
    std::fs::create_dir_all(path.parent().unwrap()).unwrap();
    std::fs::write(path, body).unwrap();
}

/// A cargo home of its own: the Holger registry, and no other credential.
fn cargo_home(dir: &Path, http: u16) {
    write(
        &dir.join("config.toml"),
        &format!(
            "[registries.holger]\nindex = \"sparse+http://127.0.0.1:{http}/crates/index/\"\ncredential-provider = \"cargo:token\"\n\n[net]\nretry = 0\noffline = false\n\n[publish]\ntimeout = 1\n"
        ),
    );
}

/// `cargo <args>` in `dir` as `who` (`None`: no credential); its exit and its output.
fn cargo(home: &Path, who: Option<&str>, dir: &Path, args: &[&str]) -> (bool, String) {
    let mut cmd = Command::new("cargo");
    cmd.args(args)
        .current_dir(dir)
        .env("CARGO_HOME", home)
        .env("CARGO_TARGET_DIR", dir.join("target"))
        .env_remove("RUSTC_WRAPPER")
        .env_remove("CARGO_BUILD_JOBS")
        .env_remove("CARGO_REGISTRIES_HOLGER_TOKEN")
        // `-Zpublish-timeout` only (the wait after a publish), on the stable toolchain.
        .env("RUSTC_BOOTSTRAP", "1");
    if let Some(w) = who {
        cmd.env(
            "CARGO_REGISTRIES_HOLGER_TOKEN",
            format!("Bearer {}", token(w)),
        );
    }
    let out = cmd.output().unwrap_or_else(|e| panic!("{cmd:?}: {e}"));
    (
        out.status.success(),
        format!(
            "{}{}",
            String::from_utf8_lossy(&out.stdout),
            String::from_utf8_lossy(&out.stderr)
        ),
    )
}

/// A crate in `dir`: its `Cargo.toml` (publishing to Holger only) and its files.
fn a_crate(dir: &Path, name: &str, version: &str, deps: &str, files: &[(&str, &str)]) -> PathBuf {
    let root = dir.join(format!("{name}-{version}"));
    write(
        &root.join("Cargo.toml"),
        &format!(
            "[package]\nname = \"{name}\"\nversion = \"{version}\"\nedition = \"2021\"\ndescription = \"lane HOL-CARGO-PUBLISH-DOOR probe\"\nlicense = \"MIT\"\npublish = [\"holger\"]\n\n[dependencies]\n{deps}"
        ),
    );
    for (path, body) in files {
        write(&root.join(path), body);
    }
    root
}

/// `cargo publish` of the crate in `dir`. cargo then waits for the version to show in the
/// index, which a publish into the chain never does before its walk: the wait is cut to one
/// second (`publish.timeout`, `-Zpublish-timeout`), and cargo says it timed out.
fn publish(home: &Path, who: Option<&str>, dir: &Path) -> (bool, String) {
    cargo(
        home,
        who,
        dir,
        &[
            "-Zpublish-timeout",
            "publish",
            "--registry",
            "holger",
            "--no-verify",
            "--allow-dirty",
        ],
    )
}

/// The `.crate` cargo packaged in `dir` (below its target directory).
fn packaged(dir: &Path, file: &str) -> Vec<u8> {
    fn find(d: &Path, file: &str) -> Option<PathBuf> {
        for e in std::fs::read_dir(d).ok()?.flatten() {
            let p = e.path();
            if p.is_dir() {
                if let Some(f) = find(&p, file) {
                    return Some(f);
                }
            } else if p.file_name().is_some_and(|n| n == file) {
                return Some(p);
            }
        }
        None
    }
    let p = find(&dir.join("target"), file)
        .unwrap_or_else(|| panic!("cargo packaged no {file} below {}", dir.display()));
    std::fs::read(p).unwrap()
}

fn sha256(b: &[u8]) -> String {
    hex::encode(sha2::Sha256::digest(b))
}

/// The lines of one crate's sparse index file, parsed.
fn index_lines(port: u16, path: &str) -> (u16, Vec<serde_json::Value>) {
    let (status, body) = request(
        port,
        "GET",
        &format!("/crates/index/{path}"),
        Some("dev"),
        &[],
    );
    let lines = body
        .lines()
        .filter(|l| !l.trim().is_empty())
        .filter_map(|l| serde_json::from_str(l).ok())
        .collect();
    (status, lines)
}

#[test]
fn cargo_publish_walks_the_intake_chain_and_cargo_install_takes_only_what_is_available() {
    if !have_cargo() {
        eprintln!("cargo is not on PATH: skipped (the toolchain container carries it)");
        return;
    }
    let r = start();
    let work = tempfile::tempdir().unwrap();
    let home = work.path().join("cargo-home");
    cargo_home(&home, r.http);

    // ── the door's config.json names the download and the publish API under the door ──
    let (status, body) = request(r.http, "GET", "/crates/index/config.json", Some("dev"), &[]);
    assert_eq!(status, 200, "{body}");
    let cfg: serde_json::Value = serde_json::from_str(&body).unwrap();
    assert_eq!(
        cfg["api"],
        format!("http://127.0.0.1:{}/crates", r.http),
        "{cfg}"
    );
    assert_eq!(
        cfg["dl"],
        format!("http://127.0.0.1:{}/crates/crates", r.http),
        "{cfg}"
    );

    let main = "fn main() { println!(\"hello from the intake chain\"); }\n";
    let v1 = a_crate(
        work.path(),
        "door-demo",
        "0.1.0",
        "",
        &[("src/main.rs", main)],
    );

    // ── an anonymous publish is challenged; a Developer may not ingest ──
    let (ok, out) = publish(&home, None, &v1);
    assert!(!ok, "an anonymous publish is refused: {out}");
    let (ok, out) = publish(&home, Some("dev"), &v1);
    assert!(
        !ok && out.contains("holger-duty-2"),
        "a Developer does not ingest: {out}"
    );

    // ── the publish is INGESTED; nothing of it is served ──
    let (ok, out) = publish(&home, Some("ida"), &v1);
    assert!(ok, "the Importer publishes: {out}");
    assert!(
        out.contains("INGESTED"),
        "cargo prints where the crate went: {out}"
    );
    let packaged = packaged(&v1, "door-demo-0.1.0.crate");
    let (status, lines) = index_lines(r.http, "do/or/door-demo");
    assert_eq!(status, 404, "nothing is listed before the walk: {lines:?}");
    assert_ne!(
        request(
            r.http,
            "GET",
            "/crates/crates/door-demo/0.1.0/download",
            Some("dev"),
            &[]
        )
        .0,
        200,
        "nothing is downloadable before the walk"
    );
    let root = work.path().join("installed");
    let (ok, out) = cargo(
        &home,
        Some("dev"),
        work.path(),
        &[
            "install",
            "--registry",
            "holger",
            "door-demo",
            "--root",
            root.to_str().unwrap(),
        ],
    );
    assert!(!ok, "cargo install finds nothing before the walk: {out}");

    // ── the chain's walk: scanned, two approvers, sealed ──
    let walked = walk(r.http, "crates/door-demo/door-demo-0.1.0.crate").expect("the walk");
    assert!(
        walked.iter().any(|s| s.starts_with("bob"))
            && walked.iter().any(|s| s.starts_with("carol")),
        "two people approved: {walked:?}"
    );

    // ── the index lists it, read from the sealed crate; cargo install installs it ──
    let (status, lines) = index_lines(r.http, "do/or/door-demo");
    assert_eq!(status, 200, "{lines:?}");
    assert_eq!(lines.len(), 1, "{lines:?}");
    assert_eq!(lines[0]["name"], "door-demo");
    assert_eq!(lines[0]["vers"], "0.1.0");
    assert_eq!(
        lines[0]["cksum"],
        sha256(&packaged),
        "cksum is the sealed bytes'"
    );
    assert_eq!(lines[0]["yanked"], false);
    let (ok, out) = cargo(
        &home,
        Some("dev"),
        work.path(),
        &[
            "install",
            "--registry",
            "holger",
            "door-demo",
            "--root",
            root.to_str().unwrap(),
        ],
    );
    assert!(ok, "cargo install from the AVAILABLE set: {out}");
    let ran = Command::new(root.join("bin/door-demo")).output().unwrap();
    assert_eq!(
        String::from_utf8_lossy(&ran.stdout).trim(),
        "hello from the intake chain"
    );

    // ── a second version on its walk is not listed: the index is the AVAILABLE set ──
    let v2 = a_crate(
        work.path(),
        "door-demo",
        "0.2.0",
        "",
        &[("src/main.rs", main)],
    );
    let (ok, out) = publish(&home, Some("ida"), &v2);
    assert!(ok, "{out}");
    let (_, lines) = index_lines(r.http, "do/or/door-demo");
    let versions: Vec<_> = lines.iter().map(|l| l["vers"].clone()).collect();
    assert_eq!(
        versions,
        vec![serde_json::json!("0.1.0")],
        "only AVAILABLE versions"
    );

    // ── a sealed version is never published again: cargo sees it in the index, and the door
    // refuses the body itself ──
    let (ok, out) = publish(&home, Some("ida"), &v1);
    assert!(
        !ok && out.contains("already exists"),
        "cargo sees the AVAILABLE version: {out}"
    );
    let meta = br#"{"name":"door-demo","vers":"0.1.0","deps":[],"features":{}}"#;
    let mut body = Vec::new();
    body.extend_from_slice(&(meta.len() as u32).to_le_bytes());
    body.extend_from_slice(meta);
    body.extend_from_slice(&(packaged.len() as u32).to_le_bytes());
    body.extend_from_slice(&packaged);
    let (status, said) = request(
        r.http,
        "PUT",
        "/crates/api/v1/crates/new",
        Some("ida"),
        &body,
    );
    assert!(
        status >= 400 && said.contains("holger-intake-10"),
        "a sealed version is immutable: {status} {said}"
    );

    // ── yank and unyank go through the door ──
    let yank = |who: &str, version: &str, undo: bool| {
        let mut args = vec![
            "yank",
            "--registry",
            "holger",
            "door-demo",
            "--version",
            version,
        ];
        if undo {
            args.push("--undo");
        }
        cargo(&home, Some(who), work.path(), &args)
    };
    let (ok, out) = yank("dev", "0.1.0", false);
    assert!(
        !ok && out.contains("holger-duty-2"),
        "a Developer does not yank: {out}"
    );
    let (ok, out) = yank("ida", "0.2.0", false);
    assert!(
        !ok && out.contains("holger-cargo-3") && out.contains("not AVAILABLE"),
        "only an AVAILABLE version is yanked: {out}"
    );
    let (ok, out) = yank("ida", "0.1.0", false);
    assert!(ok, "cargo yank: {out}");
    let (_, lines) = index_lines(r.http, "do/or/door-demo");
    assert_eq!(lines[0]["yanked"], true, "{lines:?}");
    assert_eq!(
        request(
            r.http,
            "GET",
            "/crates/crates/door-demo/0.1.0/download",
            Some("dev"),
            &[]
        )
        .0,
        200,
        "a yanked version still downloads (a lock file names it)"
    );
    let (ok, out) = yank("ida", "0.1.0", true);
    assert!(ok, "cargo yank --undo: {out}");
    let (_, lines) = index_lines(r.http, "do/or/door-demo");
    assert_eq!(lines[0]["yanked"], false, "{lines:?}");

    // SEC-REVIEW 2026-11-01 P12: a yank is its publisher's or a Repository Admin's, and only
    // an Admin undoes an Admin's yank.
    let (ok, out) = yank("ivy", "0.1.0", false);
    assert!(
        !ok && out.contains("holger-cargo-3") && out.contains("publisher"),
        "another Importer never yanks ida's crate: {out}"
    );
    let (ok, out) = yank("adm", "0.1.0", false);
    assert!(ok, "a Repository Admin yanks: {out}");
    let (ok, out) = yank("ida", "0.1.0", true);
    assert!(
        !ok && out.contains("holger-cargo-3") && out.contains("Admin"),
        "the publisher never undoes an Admin's yank: {out}"
    );
    let (_, lines) = index_lines(r.http, "do/or/door-demo");
    assert_eq!(lines[0]["yanked"], true, "{lines:?}");
    let (ok, out) = yank("adm", "0.1.0", true);
    assert!(ok, "an Admin undoes it: {out}");
}

#[test]
fn a_crate_with_a_malware_test_file_is_refused_at_the_publish_by_name() {
    if !have_cargo() {
        eprintln!("cargo is not on PATH: skipped");
        return;
    }
    let r = start();
    let work = tempfile::tempdir().unwrap();
    let home = work.path().join("cargo-home");
    cargo_home(&home, r.http);
    let eicar = format!(
        "{}{}\n",
        "X5O!P%@AP[4\\PZX54(P^)7CC)7}$", "EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"
    );
    let evil = a_crate(
        work.path(),
        "door-evil",
        "0.1.0",
        "",
        &[
            ("src/lib.rs", "pub fn x() {}\n"),
            ("assets/eicar.com", &eicar),
        ],
    );
    let (ok, out) = publish(&home, Some("ida"), &evil);
    assert!(!ok, "the publish is refused: {out}");
    assert!(out.contains("EICAR_Test_File"), "by the rule's name: {out}");
    assert_eq!(index_lines(r.http, "do/or/door-evil").0, 404);
}

#[test]
fn a_crate_pinned_to_a_known_cve_dependency_is_refused_at_scanned_by_name() {
    if !have_cargo() {
        eprintln!("cargo is not on PATH: skipped");
        return;
    }
    let r = start();
    let work = tempfile::tempdir().unwrap();
    let home = work.path().join("cargo-home");
    cargo_home(&home, r.http);
    // crates.io, offline: smallvec 1.6.0 from a local directory source.
    let vendor = work.path().join("vendor");
    write(
        &vendor.join("smallvec/Cargo.toml"),
        "[package]\nname = \"smallvec\"\nversion = \"1.6.0\"\nedition = \"2018\"\n",
    );
    write(
        &vendor.join("smallvec/src/lib.rs"),
        "pub struct SmallVec;\n",
    );
    write(
        &vendor.join("smallvec/.cargo-checksum.json"),
        "{\"files\":{},\"package\":null}",
    );
    let user = a_crate(
        work.path(),
        "door-cve",
        "0.1.0",
        "smallvec = \"=1.6.0\"\n",
        &[("src/lib.rs", "pub use smallvec::SmallVec;\n")],
    );
    write(
        &user.join(".cargo/config.toml"),
        &format!(
            "[source.crates-io]\nreplace-with = \"vendored\"\n\n[source.vendored]\ndirectory = \"{}\"\n",
            vendor.display()
        ),
    );
    let (ok, out) = publish(&home, Some("ida"), &user);
    assert!(ok, "the publish is INGESTED: {out}");
    let held = walk(r.http, "crates/door-cve/door-cve-0.1.0.crate")
        .expect_err("a crate pinned to a vulnerable dependency never becomes AVAILABLE");
    assert!(
        held.contains("RUSTSEC-2021-0003") && held.contains("smallvec") && held.contains("1.6.0"),
        "refused by the advisory and the dependency: {held}"
    );
    assert_eq!(index_lines(r.http, "do/or/door-cve").0, 404, "never listed");
}