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/a_write_outside_the_chain_is_refused.rs

//! **A write outside the intake chain is refused where the policy requires a scan** (lane
//! HOL-FIELD-2 P19, coordinator decision (A) 2026-11-01).
//!
//! The field test found side doors around the chain on the estate declaration's shape: the
//! writable `crates` repository took `cargo publish` of smallvec 1.6.0 (RUSTSEC-2021-0003) and
//! served it at once, and the raw `bundles` repository served a PUT of lodash 4.17.15. Neither
//! had an intake record, a scan or an approval. On a Holger whose policy requires a scan
//! (`security_profile: Critical`, or `promotion.intake.scan.required` naming a kind), every
//! repository outside the chain now refuses writes by name (`holger-intake-15`). The exceptions
//! are the chain's own doors (`incoming`, the scanned `oci` registries and the npm door),
//! the read-only chain views, and a repository only an Admin can read (no distribution path).
//! An opt-out (`promotion.intake.unscanned`) is refused at boot where a scan is required
//! (`holger-intake-16`), the `holger-oci-1` pattern. R1 ONE GATE: without a required scan the
//! repository is still held unless `unscanned` names it (the gate is not a configuration toggle).

// An upstream with its scanners: a gateway build (`gateway`, the downstream) refuses to start it
// (`holger-downstream-1`), so the CI's gateway set does not build it (lane hol-rel).
#![cfg(not(feature = "gateway"))]
use std::io::{Read, Write};

use holger_server_lib::{read_ron_config, wire_holger, Holger};
use traits::test_support::hold_port;

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

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

/// `scan`: the chain's `scan:` value; `unscanned`: the opt-out list.
fn start(scan: &str, unscanned: &str) -> Result<Running, String> {
    let mut last = String::new();
    for _ in 0..8 {
        match try_start(hold_port(), hold_port(), scan, unscanned) {
            Ok(r) => return Ok(r),
            Err(e) if e.contains("holger-intake-16") => return Err(e),
            Err(e) => last = e,
        }
    }
    Err(format!("server never started; last error: {last}"))
}

fn try_start(grpc: u16, http: u16, scan: &str, unscanned: &str) -> Result<Running, String> {
    let data = tempfile::tempdir().unwrap();
    let d = data.path().display().to_string();
    let raw = |name: &str, extra: &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}"){extra}),"#
        )
    };
    let writable = |name: &str, ty: &str| {
        format!(
            r#"(ron_name: "{name}", ron_repo_type: "{ty}", 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")),
         ron_writable_archive: Some("{d}/{name}.znippy")),"#
        )
    };
    let files = format!(
        r#"(ron_name: "crates-files", 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")),
         ron_data_dir: Some("{d}/crates-files")),"#
    );
    let ron = format!(
        r#"(
    repositories: [ {incoming} {quarantine} {approved} {production} {bundles} {sealed} {crates} {files} ],
    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: [ StaticToken(subject: "ida", token: Some("{ida}")), StaticToken(subject: "adm", token: Some("{adm}")) ],
        roles: {{ "ida": Writer, "adm": Admin }},
        duties: {{ "ida": [Importer], "adm": [RepositoryAdmin] }},
    )),
    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: 1,
            scan: {scan},
            unscanned: [{unscanned}],
        )),
    )),
)"#,
        incoming = raw("incoming", ""),
        quarantine = raw("quarantine", ""),
        approved = raw("approved", ""),
        production = raw("production", ""),
        bundles = raw("bundles", ""),
        sealed = raw(
            "monetize-backup",
            ", ron_require_auth: true, ron_require_role: Some(Admin)"
        ),
        crates = writable("crates", "rust"),
        ida = token("ida"),
        adm = token("adm"),
    );
    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", "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`: the status and the body.
fn request(port: u16, method: &str, path: &str, who: &str, body: &[u8]) -> (u16, String) {
    let mut c = std::net::TcpStream::connect(("127.0.0.1", port)).expect("connect");
    let head = format!(
        "{method} {path} HTTP/1.1\r\nHost: 127.0.0.1\r\nAuthorization: Bearer {}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
        token(who),
        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)
}

const REQUIRED: &str = "Some((required: [vulnerability]))";

#[test]
fn a_write_outside_the_chain_is_refused_where_a_scan_is_required() {
    let r = start(REQUIRED, "").expect("boots");
    for (path, what) in [
        (
            "/crates/api/v1/crates/new",
            "cargo publish into the writable crates registry",
        ),
        (
            "/bundles/x/lodash-4.17.15.tgz",
            "a PUT into the raw bundles repository",
        ),
        (
            "/crates-files/api/v1/crates/new",
            "cargo publish into a file-backed crates registry",
        ),
    ] {
        let (s, b) = request(r.http, "PUT", path, "ida", b"not scanned");
        assert_eq!(s, 403, "{what} is refused: {b}");
        assert!(
            b.contains("holger-intake-15") && b.contains("incoming"),
            "{what}: the refusal says why and where to upload: {b}"
        );
    }
    assert_ne!(
        request(r.http, "GET", "/bundles/x/lodash-4.17.15.tgz", "ida", &[]).0,
        200,
        "nothing refused is served"
    );

    // The chain's own door takes the upload; an admin-only repository is no distribution path.
    let (s, b) = request(r.http, "PUT", "/incoming/files/a.txt", "ida", b"hello");
    assert_eq!(s, 201, "an upload into incoming is INGESTED: {b}");
    let (s, b) = request(
        r.http,
        "PUT",
        "/monetize-backup/k/material",
        "adm",
        b"sealed",
    );
    assert!(
        (200..300).contains(&s),
        "an Admin-only repository keeps its writes: {s} {b}"
    );
}

#[test]
fn an_unscanned_opt_out_is_refused_at_boot_where_a_scan_is_required() {
    let e = start(REQUIRED, r#""bundles""#)
        .err()
        .expect("an opt-out where a scan is required is refused");
    assert!(
        e.contains("holger-intake-16") && e.contains("bundles"),
        "the boot refusal names the code and the repository: {e}"
    );
}

/// R1 ONE GATE (the architecture review §7(c) #1): the refusal used to hold only where a scan
/// was required. Now a repository outside the chain is held at the gate on every Holger with a
/// chain, and `promotion.intake.unscanned` is the declaration that opens one where no scan is
/// required.
#[test]
fn without_a_required_scan_a_write_outside_the_chain_is_held_unless_declared_open() {
    let r = start("None", "").expect("boots");
    let (s, b) = request(r.http, "PUT", "/bundles/x/notes.txt", "ida", b"hello");
    assert_eq!(s, 403, "held at the gate: {b}");
    assert!(b.contains("holger-intake-15"), "{b}");
    drop(r);
    let r = start("None", r#""bundles""#).expect("boots with the opt-out");
    let (s, b) = request(r.http, "PUT", "/bundles/x/notes.txt", "ida", b"hello");
    assert_eq!(s, 201, "declared open, so it is taken: {b}");
}