The agent — design
Class:
design· Function:agent(a sibling process to the server; produces whatstorageserves). The whole-system view isdesign.md.
What it is
The agent is the internet-side half of the airgap split. It runs where there is connectivity, pulls the packages a secure zone needs, and packs them into one signed .znippy archive that an operator carries across the boundary. The secure-side server never needs connectivity — the write happens outside the wall. Binary holger-agent (package holger-agent-cli, agent/cli); logic in agent/lib.
The connector seam — ConnectorTrait
Every source and target implements one trait (traits/src/lib.rs):
| method | purpose |
list_repositories() -> Vec<RemoteRepository> |
enumerate repos at a source |
list_assets(repo) -> Vec<RemoteAsset> |
enumerate a repo's artifacts |
download_asset(asset) -> Vec<u8> |
pull one artifact's bytes |
upload_asset(repo, path, data) |
push bytes to a writable target |
- Sources: crates.io, Nexus, Holger, a
.znippyarchive, a directory. - Targets: a
.znippyarchive, a directory, Nexus, Holger.
Each connector carries a ConnectorAuth = None | Basic { username, password } | OidcToken { token } | Mtls. Concrete connectors ship as their own crates: agent/connector/nexus (Basic auth) and agent/connector/world (crates.io / PyPI, the external-registry connector). The same ConnectorTrait symmetry means "pull from Nexus, pack to znippy" and "pull from crates.io, pack to znippy" are the same code with different connectors.
The flow
Source ──▶ [agent: list → download → pack + sign] ──▶ .znippy ──(USB/optical)──▶ server
list_repositories/list_assetsenumerate what to pull;download_assetfetches each artifact's bytes;- the set is packed into an immutable, content-addressed (blake3)
.znippy— one archive per repository version; - the operator carries the
.znippyacross the airgap; the server points a read-only repo at it and serves every ecosystem's native protocol out of it.
Hardening (the internet-side attack surface)
The agent is the component with egress, so its download path is guarded:
safe_joinrejects absolute or..-bearing upstream-supplied asset paths (an upstream can't make the agent write a cron/shell file → RCE on the internet-side host).- SSRF: the Nexus connector requires a registry-supplied
downloadUrlto share scheme+host+port with the configured base URL before attaching Basic/Bearer creds, so credentials can't leak to169.254.169.254or internal hosts; the world/pip connector restricts downloads to an https host allow-list. - Connector responses / JSON are read through capped readers; expanded archive entries go through a size-limited reader.