Changelog
All notable changes to Holger are listed here, one section per version published on crates.io. Each entry names the commit on main it comes from. The format follows Keep a Changelog, and Holger uses Semantic Versioning.
Each fixed vulnerability is listed under Security with its RUSTSEC, CVE or GHSA id. To report a vulnerability, see SECURITY.md. The full list of features, with status, date, source path and commit, is docs/v1/FEATURES.md.
[3.1.1] - 2026-11-02
Published from the main commit the tag v3.1.1 names. Changes since 3.1.0 (v3.1.0, d3d7bf2d). The products holger, holger-downstream, holger-cli and holger-ui are 3.1.1. Only the crates whose content changed are published again: holger-errcode, holger-zip-entry, holger-intel, holger-traits, holger-downstream-rules, holger-scan-worker, holger-agent-cli, holger-server-lib and the Rust (znippy), Composer, conda, deb, Docker, gem, Go, Helm, Maven, npm, NuGet, Python and rpm repositories are 3.1.1, and so is holger-fleet. Two are 4.0.0 (cargo-semver-checks 0.51 against crates.io): holger-index-rows (the new variant IndexRow::Crate on an exhaustive enum) and holger-inspect (a new public field ExecPolicy.windows_launchers on an exhaustively constructible struct). holger-sandbox is 4.0.1 (the worker tag RELEASE). holger-errcode keeps its 3.0 shape: a code's registry name is ErrCode::name() over the table NAMES, not a new field, so it is a minor (745d9e62). holger-server-lib stays on the product line, as in 3.1.0: its version is Holger's version identity (holger --version, health, the fleet's build, the scan worker's build). Every other crate keeps its version. The upload fix P12 is in both releases: 3.1.0 carries it cherry-picked (bc7c1056, 37fc0c97), and 3.1.1 has it as 3bb8bc75.
Every host rebuilds its scan worker image for 3.1.1 (cargo xtask scan-worker image, localhost/holger-scan-worker:3.1.1): a 3.1.1 server refuses every worker but its own build.
Security
Every 3.1.0 box should upgrade: the review of 2026-11-01 (SEC-REVIEW-2026-11-01) found ways past the intake chain, fixed here.
- Fixed: a double slash skipped the scanned OCI registry (SEC-REVIEW-2026-11-01 P1, 7e5061e7).
/v2//<repo>/…reached the raw registry past the scanned view, so an image could be pushed unscanned and a BLOCKED or QUARANTINED one read. Every path with an empty,.or..segment (also%2e) is now refused with 400 (holger-server-14) before any door. - Fixed: gRPC
PutArtifactStreamskipped the side-door refusal (P2, 51c59861). The streamed upload now asks the chain's write refusal before a piece reaches the backend, so a side door is refused withholger-intake-15there too. - Fixed: gRPC writes and reads skipped the intake chain on a scanned registry and the npm door (P3, 5b5226bc). gRPC puts into a scanned OCI registry, the npm door or the cargo door are refused (
holger-intake-11); gRPC reads of a scanned registry serve nothing staged, and its paths outside/v2/<repo>/are refused; the npm door may hold no storage (holger-npm-5). - Fixed: replication copied unapproved bytes into a served repository (P4, 5e0e1b91).
ReplicateArtifactsand the replicate job copy only out of production (read through the chain's view), never into the chain or into a repository that refuses raw writes; the job worker checks again, and every job kind answers which repositories it writes. - Fixed: upstream proxies served unscanned bytes (P5, 7c6ebb3f). Where a scan is required, every
*-remoterepository and everyron_upstreamsentry is refused at start by name (holger-intake-17, pointing to pull-through sources), and an upstream that names a chain repository is refused on every chained Holger. Before the upgrade, move such a repository to a pull-through source, or the 3.1.1 box does not start. - Fixed: a POM property bomb could exhaust the server's memory (D1, 9894ab8b). POM property interpolation is bounded (64 KiB per value, 1 MiB per model, 12 rounds) and fails closed: the version stays unresolved and its dependency set is held.
- Fixed: a Critical box without an intake chain had no side-door refusal (P6, a5704380).
security_profile: Criticalwith a writable or proxy repository and nopromotion.intakeis refused at start by name (holger-intake-18). - Fixed: an image naming a
subjectwas staged unscanned as an "attachment" (P7, 5602d9fb). Only an inert manifest (no image layer type, sizes declared, at most 16 MiB, no index) is an attachment; a runnable image that names a subject walks the chain like any image, and the staged-attachment view serves nothing else. - Fixed: a group repository served its members' raw backends (P8, 4f61b4ba). A group may name no chain repository, scanned registry or chain door, and no Admin-only member unless the group is Admin-only (
holger-intake-19). - Fixed: a promotion could write into a side door (P9, e99a871f).
PromoteArtifact, as a call and as a job, never writes into a side door, a scanned registry or a chain door. - Fixed: a handler-backed repository counted as a chain view (P10, 03bb6c9b). One rule decides what a chain view is (no archive, no handler, a view format), so a handler-backed deb, rpm or other repository is a side door where a scan is required.
- Fixed: a published crate could point builds at another registry (P11, 52261df0). A crate whose dependency names another registry (a
registry-indexother than crates.io, or a registry name) gets no index line, so the cargo door refuses it. - Fixed: any importer could yank any crate (P12, 6a9a5322). A yank belongs to the crate's publisher (the INGESTED actor) or a Repository Admin, and only an Admin undoes an Admin's yank.
- Fixed: a POM's classifier or type could name another path (D2, 39aa0bef). Both must be Maven path segments (no
/,..,?or#), or the member is unresolved; every closure path is checked segment by segment before a fetch. - Fixed: a set approval was not bound to what the approver saw (D4, 2f8d0237). A vote on a dependency set names the set's revision (16 hex of the sha256 over its members and their sha256, as shown); a vote with none or another is refused with 409 (
holger-depset-4) before anything is cast. HTTP?revision=, gRPCJudgeDependencySetRequest.revisionandDependencySet.revision,holger-cli intake set approve|refuse --revision, and holger-ui sends the revision it shows. A client that votes without it must send it now. - Bounded: the POM resolver (D3, 0ae58dd9). A POM is capped at 1 MiB (fetched under that cap too), BOM imports at 16 deep, and the DOCTYPE scan reads to its own
>; past a bound the version is unresolved and its set held. The parse still runs in the server process; it moves into the scan sandbox in a later release. - Narrowed: the Windows-launcher warning (W1, 19e49c69). It covers only setuptools' and distlib's launcher names, and a warning that excuses a native executable stops pip's automatic approval.
- Fixed: a signed
Packagesindex could list a.debunder another name (K1, 03589a6f). The box-signed index lists a.debonly when its control's Package, Version (less an epoch) and Architecture match its pool path; malformed and repeated fields are dropped. - Fixed: a planted temporary could redirect the private key file (K2, 8de77ea1). The key file is written through a fresh, uniquely named temporary (created new, 0600 on the handle) and renamed in, so a planted
.keytmpor symlink can neither redirect nor expose it. - Fixed: behind the front, every visitor counted as the front (F1, ba766195). From a peer in
ron_trusted_proxies, the HTTP door takes the right-mostX-Forwarded-Foras the client for the visitor tally and the admission share, and audit rows say<client> via <front>. The gRPC doors read it for the tally and the admission share too (1dee7919); gRPC audit rows still record the TCP peer. - Guarded: every write door goes through intake (dc5f9646). Every gRPC method of every proto is classified in
every_write_door_goes_through_intake(a raw door names the test that proves its refusal), every job kind says which repositories it writes, and every HTTP write verb into every kind of repository is walked.MigrationService.Startis refused on a chained Holger (holger-intake-20). - Fixed: a write outside the intake chain was served unscanned (HOL-FIELD-2 P19, 29c8242f). Where the policy requires a scan, a write into any repository outside the intake chain is now refused by name (
holger-intake-15), on the HTTP and the gRPC doors. Exempt are the chain's four repositories, the scanned OCI registries, the npm and cargo doors, the read-only chain views and Admin-only repositories. A box whose policy requires a scan refusespromotion.intake.unscannedat start (holger-intake-16). The console offers a side door its reads only. - Fixed: a raw file that is a package passed with no finding (HOL-FIELD-2 P16, c7fd81b8). A file uploaded as raw that is a package by its own manifest (a tarball's
package.json,PKG-INFOorCargo.toml; a zip's wheelMETADATA,PKG-INFOorpom.properties) is now matched as every package it names, in the server and in the scan worker. Before, lodash 4.17.15 and PyYAML 5.3 passed as raw with 0 findings. Image and archive refusals name their worst advisory ids. - Fixed: a renamed
.debpassed its source package's advisories (HOL-FIELD-2 P14, f6e4cb00). A.debis matched by its control file under its SOURCE package, so libssl3 3.0.11-1~deb12u2 (openssl) is refused with DEBIAN-CVE-2024-5535, renamed or not. Before, it passed SCANNED with 0 findings. - Fixed: a
.debwas matched against the wrong Debian release (HOL-FIELD-2 P17, 1b4e41f2).holger intel buildnames each release's packagedebian-<n>/<source>. A version that names its release (~deb12u2) is matched in that release only, so DEBIAN-CVE-2023-5363 no longer flags the bookworm package that fixed it through a trixie range. Rebuild the intel bundle to get the split. - Fixed: the repository key's file is never written through a left-over temporary (a 0644 file or a symlink in
repo-signing/), and arepo-key createthat races another never replaces its key (HOL-SIGN-2 review F1, c23d1001). - Fixed: a chain view never serves or signs an index that came through the chain. A
Release,InRelease(a replayed older one) orrepomd.xmluploaded underdists/orrepodata/and approved is not a file of the view; the view serves and signs only the index it synthesizes (HOL-SIGN-2 review F2). The key signs aReleaseonly at a plain suite word (H1). - Fixed: a client could choose the links Holger writes (HOL-DOCFIND, 15f224ab, 1878d5d1). A link Holger writes (npm packument tarballs, crates
dl) carries the door's own scheme (https onron_tls) and the request's host.X-Forwarded-ProtoandX-Forwarded-Hostare read only from the endpoint's newron_trusted_proxies(CIDRs,holger-listen-6). An estate names its front as its doors' one trusted proxy (10.44.0.1/32); with edda estate-front 0.3.4 the front writes those headers and drops a client's, so a client spoofing them getshttps://repo.<estate>links. A crates pull-through'sconfig.jsonsaysauth-requiredwhere reads are gated.
Changed (security policy)
- Publishing a bundle to downstreams goes through the bundles door (lane HOL-BUNDLES-DOOR). Since P19 an operator's
PUTinto a rawbundlesrepository is refused where a scan is required (holger-intake-15): it would be served unscanned. The chain now has a bundles door (promotion.intake.bundles,bundles_trust_policy;steps: (bundles: …)): aPUTthere is a signed Holger bundle, verified in theverify-bundlejob (release signature, approvals, every member) before it is stored for the downstreams that mirror it, and anything else is refused by name (holger-bundles-1; a misdeclared doorholger-bundles-2). Before the upgrade: declare the door and its trust policy where downstreams pull from abundlesrepository. An estate declaresbundlesas its door; its medium carries the policy (cargo xtask estate-media --bundles-trust <policy.ron>, medium parameterbundles_trust_policy), and without one everyPUTintobundlesis refused. - A Maven POM's advisories are a warning, never a block (lane HOL-DEPSET-APPROVE, bbd68300). A
.pomcarries no code: it is the descriptor Maven reads to build the dependency graph, and Maven reads the POMs of OLD versions it then mediates away without fetching their JARs (a realmvn dependency:getreadscommons-collections 3.2.1,log4j 1.2.12anddom4j 1.1POMs undermaven-dependency-plugin3.7.0). Before, such a POM was refused by its CVE (403), and Maven fails hard on a refused descriptor, so the plugin could never resolve through Holger. Now the vulnerability scanner records the POM's advisories as a WARN (named in the evidence, the dependency set and the console; scanner policyholger-scan-vulnerability/2, so stored verdicts are scanned again), and the rulecve_maxskips them. The JAR, WAR, EAR or AAR of the same coordinates is matched and refused as before (a log4j-core 2.14.1 JAR is refused with GHSA-jfh8-c2jp-5v3q). Malware, licence, provenance, secrets and inspection still block a POM. The rule is keyed on the Maven ecosystem and the.pomsuffix only (scanner::advisories_warn_only). - A Windows script launcher in a wheel or sdist is a warning (lane HOL-310-CHECK, cff80b92). setuptools and pip were BLOCKED as sdists for their PE
.exestubs (8 and 6). They pass with a warning now; every other inspection finding still blocks.
Added
- A pull is approved as ONE dependency set (lane HOL-DEPSET-APPROVE, ee3c8786, field row HOL-MAVEN-APPROVAL-WAVES). A request that pulls a package now pulls its whole dependency closure. Maven: the POMs Maven reads (parents, imported BOMs, every version the graph visits) and the JARs of the mediation winners, resolved as Maven 3.9 does (
pull_intake::pom). Formaven-dependency-plugin3.7.0 that is 221 files, exactly the set a realmvn dependency:getdownloads. npm, cargo and Python closures come from their registries' metadata (pull_intake::closures). Every file goes through the intake chain as before. The approvers see the closure as one set:GET /-/intake/sets,GET /-/intake/set?id=,POST /-/intake/set/approve|refuse?id=&reason=, the Dependency sets cards on holger-ui's Security page, andholger-cli intake sets,set <id>,set approve <id>andset refuse <id> --reason(cd14cdee). The two-person rule is unchanged per member. A member with a refusal reason refuses the set and is named (holger-depset-2). A member that cannot be resolved or scanned holds the set (holger-depset-3, fail closed). In the field, the plugin's 221 files were judged in one round instead of 30+ rounds of about 100 approvals. cargo publishis a door of the intake chain (lane HOL-CARGO-PUBLISH-DOOR, 6cf9da9f). Since P19 a chain Holger refusedcargo publishinto a writable crates registry (holger-intake-15). Now the repositorypromotion.intake.cargonames (crateson an estate, no storage of its own) is the chain's cargo door, the waypromotion.intake.npmis the npm door: a publish is INGESTED intoincoming(crates/<name>/<name>-<version>.crate) and walks the chain (scan, policy, two approvers); the sparse index lists only AVAILABLE versions, each line from the sealed crate's ownCargo.toml(the scan worker's new crate row,holger_index_rows::crates);cargo yankandcargo yank --undoare recorded for AVAILABLE versions only. A crate with a malware finding is refused at the publish; a crate that pins a dependency version with a known advisory (=x.y.z, or a binary crate'sCargo.lock) is refused at SCANNED by the advisory and the dependency's name. New codesholger-cargo-1toholger-cargo-4. The console's client setup offerscargo publish,cargo installandcargo yankfor the door again. Test:cargo_publish_walks_the_intake_chain.rs(real cargo).- pip is told why a file was refused (lane HOL-310-CHECK, cff80b92). The simple page lists a refused file as yanked with the reason, and the file answers 403 with
X-Holger-Refusaland the reason in the status line:pip install PyYAML==5.3prints "Refused by Holger" with both GHSA ids. - The front reports its doors to main, and the flow shows it (lane HOL-FLOW-FRONT, 6e836b3d, Henrik Q1 and Q4). The estate front posts its doors, served names and routes to main (
POST /-/front/report, a keyed-blake3 proof from the console x25519 pair; an older main answers 404). The flow unfolds the front at levels 2 and 3 with the same ports. A downstream's summary carries itsconsole_urlwith its own console proof, and its box links to that server's/flow. - Archive-backed deb and rpm repositories sign their metadata (
ron_archive_path):InRelease,Release.gpgandrepomd.xml.ascunder the box's repository key, like the chain views. The console's apt and dnf setup names the box's key fingerprint (a downstream's own) (HOL-SIGN-2, c23d1001). - helm push 3.18+ and oras get through a scanned registry (HOL-FIELD-2, 91813838). A pusher reads its own staged manifest back by digest, then tags it; anonymous and other readers get 404, and no staged blob is served.
- A locked crate version that is not the newest pulls through. The crate index lists the upstream versions that are not AVAILABLE as yanked, so a
Cargo.lockpin pulls exactly that.crate(HOL-FIELD-TEST, de14ed73). - Maven plugin-prefix resolution through the pull-through. The group-level
maven-metadata.xml(for exampleorg/apache/maven/plugins/) is served as the source's plugin list, somvn dependency:getand other prefix goals resolve (HOL-FIELD-TEST P13, 1ae850f0). - The ONE box's refeed carries the console's public-site switch (
console_public_site = on | off); main and twin keep it as the settingconsole.public-site, audited (HOL-PUBSITE-FEED, d8060233). /livezanswers (HOL-DOCFIND, 15f224ab).
Changed
- Peak memory is bounded. glibc's arenas are capped at 4 (
server_lib::memory::bound_allocator), and a download is read from the znippy store in pieces (stream_get,ZnippyBase::file_pieces), never whole: pip, npm, Maven, apt and dnf first (54a734a7), then Helm, gems, Go, conda, Rust read-only archives, Composer, Docker blobs and NuGet, delta chunks too (a444ec32). Composer and NuGet package lists read only each package's manifest, and a writable Rust repository streams stored crates from its hot archive (8be12ddb): peak RSS composer 1057 to 107 MiB, nuget 920 to 113 MiB, rust-hot 587 to 74 MiB. holperf on the real package sets (oden, 32 threads, 499 GiB RAM): peak RSS per set 3643 to 8449 MiB on 2026-10-31 (holger 2103f732) and 139 to 191 MiB on 2026-11-01 (holger ad6d66db) (holperf/results/2026-10-31-oden-holperf.json,2026-11-01-oden-holperf.json).cargo xtask bench-readme checkis red above 512 MiB per set. - No intel is said by name. A box with no advisory database, or one past
intel.block_after_days, still holds every package (it always failed closed), and now says so as "no vulnerability data (HOLGER-INTEL-MISSING)" or "vulnerability data N d old (HOLGER-INTEL-EXPIRED)", not as a finding (bfc198cc, bf78828c, 4cfc055f). - A storage failure at pull ingest is retried, never a refusal for good (lane HOL-ENOSPC-SETS, 7e14c026, field row HOL-PULL-ENOSPC-BLOCK). A full disk or another store error while a pulled file is ingested is
store-failed, and the next pull tries again. Before, the coordinate was BLOCKED for good. GET /-/sandbox/probelooks into Heimdall's runner: the worker's probe runs as one job of the server's runner, and the answer names the engine (HOL-HEIMDALL-06, d0899f40).- The console's Disks pane says that a repository's storage folder not made yet is created at the first upload, not a fault (
AdminUnresolved.made_at_first_upload, 68aa7d69). - The one-way appliance (
downstream,pid1) links no WireGuard dialer: it builds with crates.io korp-installer 0.2.11 withoutwg-identity(HOL-311-KORP, aeff7466, 7edfb580). first-sign-in.urlis removed once it is used, and at a start on a box that has an admin (HOL-DOCFIND, 15f224ab).
Fixed
- Two pulls of one coordinate at once never block it. cargo asks for the index and the download together; the second pull now walks on from where the first left it, where it was BLOCKED with SCANNED to SCANNED (ryu 1.0.23 in the field) (HOL-FIELD-2, 4f336f1d).
holger-server-libbuilds again with its default features (87341622) and withmetered,meter-guests(16812888, a stray.with_repo_key(..)from the HOL-SIGN-2 merge); the metered set is a step of the full CI set.- The feature sets
gateway(sync, fetch, downstream-wg, supply-chain, gateway) and the combined set (supply-chain, jobs, online, sign, auth-passkey, estate, fmt-all, pull-apk) are green and are steps of the full CI set (70903647, 4df743ea); clippy-D warningsis clean on xtask, holger-agent-cli and server-lib with all features (ed63b753, bf17f87b);xtask/tests/lone_clone.rsreads the whole publish list again (8484f3e4). holger-ui --versionnames its own version and commit (the published 3.1.0 printedholger-ui 3.0.0) (holger-ui b9fdf8d).
Documentation
- docs/v2: chapters 3, 4, 5, 9, 10 and 11 filled from v1 and the code on
main, with the client setups for pip, npm, cargo, Maven, apt, dnf, OCI, Go and Helm (HOL-DOCS-V2, 024f3b1a), and the gated cargo pull-through setup in chapter 3 (78f32d95). .nornir/holger-error-codes.mdis generated from the error registry (336 codes in 42 subsystems), with a guard test (15f224ab).- Real Maven measured: 3.9.5 and later retry a 503 three times and honour
Retry-After; 3.8.8 and 3.9.0 to 3.9.4 do not. The 30 s Maven hold stays (15f224ab). - Tests: real pip, npm, cargo and apt over HTTPS with a private CA and gated reads (
real_clients_over_https_with_a_private_ca.rs, 78f32d95).
[3.1.0] - 2026-11-01
Published from the main commit the tag v3.1.0 names. Changes since 3.0.0. The products holger, holger-downstream, holger-cli and holger-ui are 3.1.0. Only the crates whose content changed are published again: holger-scan-worker, holger-intel, holger-zip-entry, holger-index-rows, holger-server-lib, holger-traits (its README) and the Composer, deb, Helm, NuGet and rpm repositories are 3.1.0; holger-job-runner and holger-sandbox are 4.0.0 (cargo-semver-checks against 3.0.0: a new field on an exhaustive struct, a new engine variant). holger-server-lib stays on the product line although cargo-semver-checks finds new fields and variants in its flow model and protos: its version is Holger's version identity (holger --version, health, the fleet's build, the scan worker's build). Every other crate stays 3.0.0.
Security
- Fixed: a known vulnerability in a pulled package was not caught. A package pulled through the pull-through (a Python sdist, an npm tarball, a Maven jar, a crate) was matched against no ecosystem, so the advisory scan found nothing: PyYAML 5.3 and lodash 4.17.15, each with a known CVE, passed the scan and were served. A pulled package is now scanned in its source ecosystem (pip for every Python file, npm and Maven with their coordinates, cargo), and a known CVE refuses it as for an uploaded one (HOL-FIELD-TEST P11, ddc04aab; a real-record guard holds PyYAML 5.3 to both its critical advisories, CVE-2020-1747 and CVE-2020-14343, 2574fc6f). Every 3.0.x box that pulls through should upgrade: what it pulled before was not matched against its ecosystem.
- Fixed: an upload with a known vulnerability passed as raw (Log4Shell). A package uploaded under a chain view was scanned as raw, so the advisory scan found nothing: the real log4j-core 2.14.1 (CVE-2021-44228, Log4Shell) uploaded under
maven/passed with 0 findings, and deb, rpm, Helm and pulled APK packages had the same gap. An upload is now scanned as its format with the coordinates its path names (deb, rpm, Maven with group:artifact, Helm), a pulled APK as its repository's distribution, and a package file (.jar .pom .deb .rpm .crate .gem .nupkg .apk .conda .war .ear) that would reach the CVE scan as raw is held (holger-no-ecosystem), never passed (HOL-FIELD-TEST P12, bc7c1056 + 37fc0c97, cherry-picked). Every 3.0.x box should upgrade. - Fixed: an offline downstream could dial out. In a downstream build, an explicit
network: Offlinethat also named an upstream left the process pinned to that upstream, so it could still make the call. Such a declaration is now refused at start (holger-offline-2), and an offline downstream is fully offline: the process makes no network call (HOL-PRODUCTS-8, 4a2b3b79).
Changed
- Every host rebuilds its scan worker image for 3.1.0. The worker image is tagged with Holger's release,
localhost/holger-scan-worker:3.1.0(it was:0.1.0for every version), every scan report names the worker's build (its version and scanners), and the server refuses a worker that names no build, another version or fewer scanners, by name and with the rebuild command, where an old worker blocked every artifact with "the scan worker sent no sbom record" (HOL-FIELD-TEST P1, 3a6eeefb). After the upgrade, runcargo xtask scan-worker image(podman) orcargo xtask scan-worker stage(a medium's rootfs) on every host; a RON that namesimage: "localhost/holger-scan-worker:0.1.0"names the new tag. - A pip project page whose files wait for approval answers 200 and lists those files as yanked, with the reason (PEP 592 / PEP 691
yanked, PEP 503data-yanked), where 3.0.0 answered 404. pip skips a yanked file unless it is pinned exactly, so a pinned install (requests==2.32.3) through the pull-through now pulls exactly that file, a wheel included, and not only the newest sdist (HOL-FIELD-TEST P2, cb9773f9). A client that read the 404 as "not here yet" now sees the project with no installable file. - A pending npm package answers 200 with its unapproved versions listed as deprecated, with the reason, where 3.0.0 answered 404. Their tarballs go through Holger's door, so a pinned npm version that is not
latestpulls through (HOL-FIELD-TEST, 58e18660). - Heimdall 0.2 (lane HOL-HEIMDALL-02, 02deccdc):
nordisk-heimdall-core"0.2" from crates.io; the job results are Heimdall's signed wire v2, and producer and verifier moved in one build (a 0.1 result is refused). - The flow (HOL-FLOW-SMART, Henrik, 2d945aee): three zoom levels of one model. With no
openit draws the machines and the upstream sources (the Overview);?open=main,lab-1unfolds those machines with their part of the RON;?open=allis the full RON, on/-/flow.svg,/-/flow.jsonandAdminService.FlowSvg(FlowSvgRequest.open,FlowSvgReply.links). Every node names its box, lines cross a machine border at ports, the diode drop area and a one-way inbox are drawn, a downstream line says declared/paired/seen. The schema isholger.flow/v2. - The sync report (HOL-SYNC-SKEW, 50cbe685): a downstream reports a summary of its own RON (
DownstreamSummary) under its own proof (summary_proof); the signed report text is the 3.0.0 text, so a 3.0.0 and a 3.1.0 Holger hear each other's reports. A stray downstream seen long ago is grey with a?; the diode and inbox counters come from the audit; the console is seen per server.
Added
-
The front unfolds in the flow, and another Holger leads to its own flow (lane HOL-FLOW-FRONT, Henrik's Q1 and Q4 of HOL-FLOW-SMART): the estate front reports its doors, the names it serves and where each goes to main (
POST /-/front/report, proven under a key derived from the front's and the box's console keys; an older main answers 404 and the front carries on), main keeps the last report and the flow draws the front unfolded at levels 2 and 3 with the same box and port rules; a front that never reported stays one closed box. A downstream's summary carries its console's address with its own proof (console_proof, so a 3.1.0 upstream still opens the summary), and its box links to that server's own flow (FlowLinkwith the console's/flow); with no known console there is no link. New error codeholger-front-5. -
promotion.intake.sandbox: (engine: heimdall, heimdall: (…))(featureheimdallofholgerandholger-server-lib): the scans and the bundle verifies run as jobs of Heimdall's protected runner (nordisk-heimdall-container 0.2: Landlock, Heimdall's seccomp profile, the worker image verified at every start), following Heimdall's scan-worker job contract; where Heimdall cannot start, youki-plain, then crun overrootfs(never for a root process: there a job's root maps to the host ids 100000/65536, Heimdall 0.2.1with_host_ids), each said by name. The Security page andholger jobsshow the runner's features and Heimdall's kernel report; strict CDS warns when the runner is not Heimdall. -
Heimdall's released verify-bundle guests (heimdall-guests-0.2.1) vendored under
guests/heimdall/0.2.1/with their pins, SLSA statement and signatures, loaded as the split pair under korp-root-ca (holger-job-runnerfeatureheimdall-wasm). -
The appliance medium carries Heimdall (the static shim 0.2.1, the released verify-bundle guests, the worker tar), the estate declares
engine: heimdall, and the import door verifies every bundle in the split wasm guest inside the worker (nordisk-heimdall-wasm 0.2.1, strict DSSE) (HOL-HEIMDALL-03/04, 9cfbf221). -
A crates.io
holgernames the commit it was published from (.cargo_vcs_info.jsongit.sha1) instead ofunknown(GUN-VERSION-STAMP, 81c13781). -
The README's speed table comes from holperf on real packages (pip, npm, Maven, Helm, apt, dnf, OCI), with the cost of the first index and the peak RSS beside each number (
cargo xtask bench-readme, README-BENCH, c694a3f0). -
The deb and rpm views serve signed metadata under the box's repository key (
InRelease,Release.gpg,repomd.xml.asc, the key at/-/repo-key.asc), so apt and dnf check it without[trusted=yes]orgpgcheck=0(HOL-SIGNED-APT-DNF, HOL-FIELD-TEST P4, f9257ddd). Operators runholger repo-key createonce per box; a box without a key serves unsigned metadata and warns. -
The first admin of a plain start with a passkey method comes from a single-use first-sign-in link in
<data>/holger-passkeys/first-sign-in.url: its passkey becomes the admin (HOL-FIELD-TEST P3, 8b5e367c). -
The sync doors (HOL-PRODUCTS-5):
GET /-/sync/keyanswers an authenticated identity only (401 without a credential), andPOST /-/sync/ackchecks both the downstream's bearer (itstoken_file) and the ack's signature; the doors' table says so (f45d99bf, db29fb7c). -
The WireGuard tunnel comes up only when Holger starts, never when the intake chain is built (for example by
holger sync key) (HOL-PRODUCTS-9, cf0459de).
Fixed
- A scanner error at ingest is a scan failure, retried, not a refusal for good. A pull refused only by scanners that could not run (
scanner-error,scanner-not-configured, for example a stale worker) was recorded as a permanent refusal; it is now "scan-failed, retry": nothing is stored or served in the meantime (fail closed), and the next request scans it again once the scanners can run (HOL-FIELD-TEST P8, e59af7e7). - A main laid again after its death is held for its recharge again and keeps its sync key: the medium's seeded Sigstore trust root no longer counts as history of its own (HOL-REBUILT-SYNC-KEY, bcc23545).
holger intel buildreads the real OSV dumps: an OSV dump has its own member ceiling (npm'sall.ziphas 230994 members), and an affected entry with no package is dropped with the record kept (8a0b9afd); the alias merge runs per (ecosystem, package) in linear time, so a build over the real feeds ends (100000 records in seconds, 6416a8ce).- The tests that were red on main (HOL-310-RELEASE): the twin test's fixed loopback pair, the source scans after the fmt sweep, the front's console token mode, the
boundary: Fetchdownstream tests after a one-way build, the server-lib--all-featuresclippy.
[3.0.0] - 2026-10-31
Published from the main commit the tag v3.0.0 names. Changes since 2.0.0. A major release: the defaults are the NCSC P1 safe defaults, Holger is four products, and the ORIGINAL and OPEN suites are retired.
Crate versions
- Every holger crate is 3.0.0, and so is the web console
holger-ui(holger-ui repo). - New on crates.io:
holger-cli(the thin client with the offline tools), the productsholger-downstream(a thin crate overholgerwithdownstream; crates.io's holger-downstream 2.0.0 was the rules library, nowholger-downstream-rules) andholger-ui(the web console; crates.io's holger-ui 0.1.4 was the old egui console),holger-ui-server,holger-public-site,holger-scan-workerandholger-job-runner. Heimdall's job API comes from crates.io (nordisk-heimdall-core0.1.0, fe3e2f75), so no published crate names a git or unpublished path dependency.
Changed (breaking)
- Four products, one crate each:
holger(the server),holger-downstream,holger-ui,holger-cli; every product has the extrasall-formats,online,federation,full(00b40dcd). The suites ORIGINAL and OPEN, the tiersminiandembeddedand the bins holger-embedded, -mikro, -full, -bundle, -verify, -agent are retired (00b40dcd). - NCSC P1 safe defaults: a bare door is HTTPS unless it is this machine (holger-listen-1/2, a5c3c57c); no profile runs without its audit log (holger-audit-13, 44038a07); a Security Approver is invited only from a stepped-up passkey session (holger-auth-6, c5cd8171); the sandbox and clamd are required where the intake chain runs (holger-sandbox-14/-15, 5b9edc10, 2f57bf3e); every socket asks the egress check first and every refusal leaves an audit row (NCSC P1 item 5, a2aec0a6).
- The admin plane has its own listener (
ron_admin_url: AdminService, PromotionService, AuditService, TokenService) (NCSC P2, c4d577ec). - Untrusted work runs as jobs behind Heimdall's job API: bundle import and scans are jobs of the interim ContainerRunner (d276bdc1).
Added
holger-cli bundle allow-first-importand the console's First import form (c39b26ef).- A plain server invites its downstream directly (
holger-cli downstream invite, b03ead25). - The export carries only what decides (
export_profile, NCSC P2, 431143e1); mTLS doors check a CRL (ron_tls.crl, NCSC P3, 55befeba);cargo xtask release-attestnames every shipped artifact (NCSC P3, e5b4a99d); the system threat model, docs/v2/threat-model.md (325801eb). holger-index-rows: the scan worker's bounded per-package index fields on the scan wire (HOL-PERF-2, a4bf77e6).
Security
- znippy-common 0.10.3: a whole-body read reserves its result and reads stored chunks in place (a 64 MiB crate download allocates 67.5 MiB, was 220.7 MiB).
- znippy-plugin-maven 0.10.1 moves to quick-xml 0.41, and quick-xml 0.36.2 leaves the lock (RUSTSEC-2026-0194, RUSTSEC-2026-0195). In Holger, quick-xml 0.36.2 was reached only through dev-dependencies (tests), not through the shipped server (73a5e5fb, 69bbe575).
- crypto-bigint 0.5.5 (RUSTSEC-2026-0387) is ignored in
deny.tomlwith its reason: it comes only through the PGP verifier (pgp0.21.0, the newest), where Holger uses public keys only, and no fixed 0.5 release exists. The estate locks follow monetize to skidbladnir 0.2.3 (f3f3a023). - The front carries only Holger's own gRPC services on the repository host, so the identity core is never reached from the internet through the front (48600b3e).
- The scan sidecar bounds each job's processes with a cgroup
pids.max(2d5dead9, eabdb3c1). - An invite needs a fresh step-up (nornir-identity 0.2.2) (5ba2272c).
Added
- holger.rs's front page names the release key (its key id) under the credit and links to
slsa/(ab668791). - The release signing key in
slsa/: the owner's YubiKey key (PIV slot 9c, P-256, PIN once, touch always), key idd24d3785e8281bcd37e8f2c40a3a85e8152d3ce2200a5c929a1a5ca636e8249e, with the YubiKey's attestation of it and the attestation CA (67154e42). - SLSA Build L3 release provenance:
docs/v1/SLSA.md, the release key inslsa/, the attestations inattestations/, and a test that the provenance scanner verifies such a release bundle (25e9033b). - The front's downstream interface logs its relay counters (48ac2fe7).
- Holger's guest declares its front's downstream port (udp/51830), so the ONE box opens only declared ports (09525b5d).
Changed
- holger-server-lib and the
holgerbinary are clippy clean under-D warningsat every feature set a build uses (f0d43bcf, 2812762c, 0d665955). - The root takes monetize-proto 0.3.3 and estate-front 0.3.0 from crates.io (10adba30).
- holger-provenance leaves its 11 MB test fixture out of the crates.io package, as the 2.0.0 package already did (7a4f5fce).
Fixed
- A dead main is rebuilt from its twin again (3df55b53).
- A lean main (no public IPv4) that the ONE box lays again is re-admitted at its utility address (1bc2d71f).
- The promotion gate refuses over gRPC
PromoteArtifact(81309576). holger-cli artifacts putstreams a file of any size (63f82f3f).- holger-server-lib's default build compiles again (27049467).
- An endpoint the admin names wins over the front's own address in a WireGuard invite (b892cf05).
2.0.0 - 2026-10-29
Published from 87e5ada5, which is main at 7bebb1e6 plus one packaging commit (the large test fixture left out of the package). Changes since the 0.6.10 crates (2026-08-23).
Holger 2.0 is a security gateway: every artifact passes an explicit intake chain before it is served, and the open core builds and installs with plain cargo.
Security
- The intake chain: each artifact has one state from UNTRUSTED to AVAILABLE, on HTTP PUT, native uploads and gRPC, and is served only when AVAILABLE (99daae77).
- Separation of duties: Developer, Importer, SecurityApprover, RepositoryAdmin and Auditor; the importer never approves its own import (17bda3af).
- File and archive inspection on every ingest (zip bombs, nesting, path traversal), and ClamAV and YARA hooks (b231aa9e).
- Every scan runs in a hardened rootless container, never in the server (1998749a, babf13c8).
- The offline provenance verifier: DSSE, in-toto, SLSA, and Sigstore keyless (Fulcio chain and identity), PEP 740, npm attestations and cosign (5eeac4fd).
- Offline vulnerability intelligence: OSV, NVD and KEV as a signed bundle, with Wolfi and Chainguard advisories (c643b9a7, d605b919).
- One tamper-evident audit trail per artifact (a hash chain with signed checkpoints), and its SIEM export (2c0d2eca).
- AI models are artifacts, and pickle files are blocked by default (7bb56235).
- The public HTTP door and the gRPC door speak TLS 1.3 only (831a8dee).
- Holger runs on edda's shared identity core, with a
__Host-HttpOnly session cookie (d47e5b15). - The security review of 2026-09-11 found 40 defects, and all 40 are fixed, each with a test that failed first (5ad190d8;
docs/v1/security-review-2026-09-11.md). -
Dependency advisories fixed in the lock:
- rustls 0.23.46 (RUSTSEC-2026-0285, RUSTSEC-2026-0346, RUSTSEC-2026-0348, RUSTSEC-2026-0349) (61954cd6);
- wasmtime 49.0.3 with cranelift 0.136.3 (RUSTSEC-2026-0353 to RUSTSEC-2026-0364) (61954cd6), then wasmtime 49.0.4 (RUSTSEC-2026-0378, RUSTSEC-2026-0379) (0b829a36);
- the plugin host moves from wasmtime 27 to 48, which clears 15 of the 17 wasmtime advisories that
deny.tomlhad accepted (1bd8c086); the wasmtime 45 copy of a dev-dependency (RUSTSEC-2026-0222, RUSTSEC-2026-0269) later leaves the graph (5f9898f9); - zip 9.0.5 in bench-scenarios and znippy-rust (RUSTSEC-2026-0367, RUSTSEC-2026-0368, RUSTSEC-2026-0369) (61954cd6);
- rustls-pemfile (RUSTSEC-2025-0134, unmaintained) is gone: PEM is read with rustls-pki-types (61954cd6);
- h2 0.4.19 (RUSTSEC-2026-0258), and quinn-proto, crossbeam-epoch and webbrowser (bcdf9b64);
- zbus 6 through skidbladnir 0.2.1, which leaves the unmaintained zbus_names, zvariant and zvariant_derive behind (a5b97301).
Added
cargo install holgerand a plaincargo build --releasegive a working server, and a lone clone builds and serves (80ec7d64, 81085f58).- Tier 1: a plain cargo build carries the intake chain, configured in one RON shape (sources, steps, rules, gates) (baa48b3a).
- Pull-through through the intake chain for OCI (cgr.dev), Python (PyPI, Chainguard Libraries), npm, Maven, crates and APK (Alpine, Wolfi) (77477911, ce1d6a13).
- npm through the security gateway (1027710e), and
twine uploadinto the intake chain (513dcac5). - The airgap:
holger-server exporttakes the AVAILABLE set with its evidence and signed attestations, and the secure side imports and verifies it (297c3efb);cargo xtask demo airgapruns the demo (0c73355d). - The sync protocol (signed sync manifests with generations), behind a default-off feature (be9cb743).
- The on-premises downstream gateway:
cargo install holger --features downstream(7b1b68fe), and WireGuard downstreams. holger-cli, the thin gRPC client; every console call is a CLI command (5022717e).- The estate board, the Security page, buyer pages, and the shared buy flow.
Changed
- The licence is AGPL-3.0-only plus a commercial licence, owned jointly by Vetra Automation AB and Ignalina ApS (d936a182). Versions published on or before 2026-10-02 keep the licence they were published with (mostly MIT).
- The root workspace is the open core; the estate parts are a separate
estate/workspace (c804ffc9). - Airgap is the default: with no upstream named, Holger is offline (d72706dd).
HOLGER_QUARANTINE_PROPERTYis retired and refuses the boot (holger-intake-14); the hold is the intake QUARANTINED state (0cb5f11f).- Everyone registers their passkey again after this release, because sessions move to the shared identity core (d47e5b15).
Known advisories
- RUSTSEC-2023-0071 (
rsa, no fixed release) is ignored with its reason: Holger performs no RSA decryption on attacker-supplied data. - The packaged
Cargo.lockholds crypto-bigint 0.5.5 (RUSTSEC-2026-0387), which is triaged under Unreleased. It holds no quick-xml.
Earlier versions
Before 2.0.0, the server was published on crates.io as holger-server-cli, beside holger-server-lib, holger-traits and the other holger-* crates. These versions are listed by their publication date on crates.io and by the git tags:
- 0.6.10 (2026-08-23): tags
holger-server-cli-v0.6.10and the other per-crate-v0.6.10tags (86cb764b). - 0.6.9 (2026-07-17), 0.6.7 (2026-06-26), 0.6.6 (2026-06-19) and 0.6.5 (2026-06-18).
- The git tags
v0.7.1(2026-06-15, 58fb6a55) andv0.7.0(2026-06-13, d674434c) mark workspace releases; noholger-server-cli0.7 was published. - 0.6.4 (2026-05-29), 0.6.3 (2026-05-27), the tag
v0.6.2(2026-05-27, a16277b1), and 0.5.1 (2026-05-25, tagv0.5.1, 18cb73f9).
The holger crate names 0.0.1 (2025-08-01) and 0.2.1 (2025-08-11) were published from an earlier repository, whose history is not part of this one.