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}");
}