hive-ci: keep the two load-bearing properties, drop the retelling

The module header restated `docs/scheduler/ci.md`'s credential-isolation
paragraph nearly word for word, and re-told the nix-sandbox gotcha that
`docs/process/gotchas.md` owns — both immediately above lines that already
pointed at those docs.

Measured, not eyeballed: 6-gram intersection between this file's comments
and `docs/scheduler/ci.md`, 74 -> 24.

Kept, because neither is visible from the config that implements it and a
reader changing this file needs both:

  * the container has its own network namespace, not the host's, so a PR's
    nix build cannot reach host-loopback services — a prompt-injection
    boundary rather than a preference;
  * the forge ADMIN token never enters this container; only a registration
    token is handed in.

Each is now one sentence naming the property and its consequence, with the
mechanism behind a `docs/x.md::Section` pointer whose heading was checked
to exist (`Container isolation`, `Container design`).

Also restated in the present: the container is non-ephemeral because the
runner writes its registered credentials on first registration and reuses
them, which is why an ephemeral one would re-register every restart. That
was previously a description of what gitea-actions-runner does, with the
reason left implicit.

Comments only; no behaviour change. Refs #3901.
This commit is contained in:
atlas 2026-09-02 05:31:40 +02:00 committed by mara
commit 55145ff8e8

View file

@ -51,30 +51,26 @@ let
in
{
# Forgejo Actions runner in a `hive-ci` nixos-container.
# Uses a private network namespace (bridge-connected, not host netns)
# so CI build scripts cannot reach host-loopback services (dashboard,
# forge internal port, etc.) — a key defence against prompt-injection
# via PR nix builds. The runner reaches the forge via the
# gateway at `http://${forgeCfg.domain}` (resolved to the bridge IP
# via `networking.extraHosts`; gateway port 80 is always open on the
# bridge; `addSSL = true` means HTTP is served alongside HTTPS without
# a redirect). See docs/networking/network.md.
# Container is non-ephemeral: the runner's registered credentials
# survive restarts (gitea-actions-runner writes them to its stateDir
# on first registration and reuses them on every subsequent start).
#
# Credential isolation: the forge admin token (`forge-core-token`)
# never enters the hive-ci container. hive-c0re holds it and performs
# all forge API calls (runner validation + registration-token mint,
# in `forge/ci_runner.rs`); via hive-priv it writes only the runner
# registration token to the host env-file `/run/hive-ci/runner-token`,
# which the container bind-mounts read-only. The container never has
# access to the wider admin token.
# ⚠️ Two properties here are load-bearing and neither is visible from the
# config that implements it:
#
# Nix builds inside the container use the shared /nix/store (standard
# nixos-container behaviour) with sandbox-fallback = true, because
# nspawn containers can't create the user-namespaces that nix sandboxing
# requires. See docs/process/gotchas.md.
# - The container gets its OWN network namespace, not the host's, so a
# PR's nix build cannot reach host-loopback services. That is a
# prompt-injection boundary; moving this to host netns for convenience
# removes it. Reachability of the forge from inside:
# `docs/networking/network.md::Container isolation`.
# - The forge ADMIN token never enters this container — hive-c0re holds
# it and hands in only a registration token. Full flow:
# `docs/scheduler/ci.md::Container design`.
#
# Non-ephemeral on purpose: gitea-actions-runner writes its registered
# credentials to its stateDir on first registration and reuses them, so an
# ephemeral container would re-register on every restart.
#
# Nix builds here use the shared /nix/store with `sandbox-fallback = true`
# — nspawn containers cannot create the user namespaces nix sandboxing
# wants: `docs/process/gotchas.md`.
options.services.hyperhive.deploy.forgejo.ci = {
enable = lib.mkOption {