fix(#2911): keep the forge token out of argv

`forge_git_url` spliced `core:<token>@` between scheme and authority, and
that URL is a process argument. `/proc/<pid>/cmdline` is mode 0444 —
world-readable — so the core admin token, which provisions every agent's
forge account, was published to any local user for the lifetime of each
git child. Seven call sites built such a URL.

The credential now travels in the environment instead:
`git_command_authed` sets `http.extraHeader` via `GIT_CONFIG_*`, which
git reads exactly like a config file, and `/proc/<pid>/environ` is 0400 —
owner-only. Same credential, materially smaller audience. The remote is a
plain `http://forge/<org>/<repo>.git`, and `forge_git_url` no longer takes
a token, so the old shape cannot be rebuilt by accident.

`knowledge`'s clone was the one place a credentialed URL was stored as a
named remote — git persists the clone URL into `.git/config`, so the
token sat on disk and every later `pull` authenticated from there. That
is the case `forge::repos::push_config` documents as forbidden ("the
tokenised URL ... deliberately never stored as a named remote"). `pull`
now rewrites `origin` to the plain URL first, which also scrubs the
persisted token from existing deployments, and authenticates from the
environment when a token is available. The repo is public, so the pull
still works without one.

Three call sites also stopped spawning `Command::new("git")` directly,
so they honour the `HYPERHIVE_GIT` path the NixOS module bakes in and
the `kill_on_drop` every other git spawn gets.

The two URL-shape tests now assert the *absence* of a credential, and a
new one decodes the header back to `core:<token>` — without that, a
malformed header would leave every forge operation silently anonymous
with the other assertions still green.
This commit is contained in:
atlas 2026-08-02 13:21:42 +02:00
commit 44572d1e1a
7 changed files with 134 additions and 56 deletions

View file

@ -17,7 +17,7 @@ use anyhow::{Context, Result};
use hive_host_sock::{HostResponse, ReconcileDirection};
use super::{CONFIG_ORG, core_token, forge_git_url, is_present};
use super::{CONFIG_ORG, core_auth_header, core_token, forge_git_url, is_present};
/// Scratch ref the forge `main` is fetched into — outside the normal
/// branch/tag namespace so it never collides with real refs.
@ -36,10 +36,11 @@ async fn fetch_forge_main(agent: &str) -> Result<PathBuf> {
if !dir.join(".git").exists() {
anyhow::bail!("agent `{agent}` has no applied config checkout");
}
let url = forge_git_url(&token, &format!("{CONFIG_ORG}/{agent}"));
crate::lifecycle::git(
let url = forge_git_url(&format!("{CONFIG_ORG}/{agent}"));
crate::lifecycle::git_authed(
&dir,
&["fetch", "--force", &url, &format!("main:{FORGE_MAIN_REF}")],
&core_auth_header(&token),
)
.await
.context("fetch forge config main (does forge main exist yet?)")?;