#412 made the GitHub host configurable on the projects/review inventory path with the pin total there. This issue collects the seams that PR deliberately left outside its scope, plus residuals from its step-0 security review.
Unpinned gh callers (still github.com-shaped)
These shell out to gh without githubauth.Runner, so they follow ambient GH_HOST (or default to github.com) and ignore [github] host:
internal/branch/branch.go:341,364,508
internal/pr/ref.go:219 (and ref.go:246-248 has its own github-only URL parse)
internal/pr/session.go:195, internal/pr/launch.go:548, internal/pr/search.go:68
internal/doctor/doctor.go:195 — also the one seam that renders raw gh stderr into a Check Detail (bounded by termsafe.SafeLine at internal/cli/doctor.go:120; worth an inline comment naming why it is allowed, since doctor is what an operator runs when the new host misbehaves)
Docs after #412 scope the pin claim honestly ("the projects/review inventory path"); this issue is the widen-or-document decision for the rest.
Config-derive the gitea host in canonicalHost
internal/projects/project.go canonicalHost still hardcodes git.sjo.lol → "gitea". A personal hostname baked into a public binary; it should derive from [review.gitea].host (or a projects-side equivalent) the same way the GitHub arm now derives from [github].host.
Security-review residuals (step-0 review of #412)
- Token scrub is empty-string, not unset —
exec.RunWithEnv can only add vars, so the scrub sets GH_TOKEN="" etc. gh (2.98.0) treats empty as unset, but that is gh-internal behavior; a filtered-env seam (RunWithEnvFiltered or building cmd.Env from a filtered os.Environ()) would make the scrub a property of the process env. Descendant processes (gh repo clone → git → credential helper) inherit the empty vars today.
--json inventory carries no degradation signal — notes are stderr-only and projects list --json emits a bare array, so a wrong-host/degraded run is indistinguishable from a small account to a script. Consider {repos, notes} or a nonzero exit when a host contributed zero rows plus a note.
Repo.Host is a token, not a hostname — the clone tree (Dir/github/<owner>/<name>) persists across a [github].host flip; a stale clone whose origin read fails can collide with a new-host clone at the same dest with no operator tooling to resolve it. Stamping the FQDN and deriving the badge for display would fix identity durability.
NewGitea renders an invalid host with %q — the one config-value render on a rejection path; make it categorical like githubauth.ResolveHost.
- Linux note —
os.UserConfigDir() honors XDG_CONFIG_HOME, so on Linux the config path (and thus the pinned host) is env-steerable; the token scrub is the bound that covers it, worth a line in the threat-model comment.
Aside
#412 made the GitHub host configurable on the projects/review inventory path with the pin total there. This issue collects the seams that PR deliberately left outside its scope, plus residuals from its step-0 security review.
Unpinned gh callers (still github.com-shaped)
These shell out to
ghwithoutgithubauth.Runner, so they follow ambientGH_HOST(or default to github.com) and ignore[github] host:internal/branch/branch.go:341,364,508internal/pr/ref.go:219(andref.go:246-248has its own github-only URL parse)internal/pr/session.go:195,internal/pr/launch.go:548,internal/pr/search.go:68internal/doctor/doctor.go:195— also the one seam that renders raw gh stderr into a Check Detail (bounded bytermsafe.SafeLineatinternal/cli/doctor.go:120; worth an inline comment naming why it is allowed, since doctor is what an operator runs when the new host misbehaves)Docs after #412 scope the pin claim honestly ("the projects/review inventory path"); this issue is the widen-or-document decision for the rest.
Config-derive the gitea host in
canonicalHostinternal/projects/project.gocanonicalHoststill hardcodesgit.sjo.lol→"gitea". A personal hostname baked into a public binary; it should derive from[review.gitea].host(or a projects-side equivalent) the same way the GitHub arm now derives from[github].host.Security-review residuals (step-0 review of #412)
exec.RunWithEnvcan only add vars, so the scrub setsGH_TOKEN=""etc. gh (2.98.0) treats empty as unset, but that is gh-internal behavior; a filtered-env seam (RunWithEnvFilteredor buildingcmd.Envfrom a filteredos.Environ()) would make the scrub a property of the process env. Descendant processes (gh repo clone→ git → credential helper) inherit the empty vars today.--jsoninventory carries no degradation signal — notes are stderr-only andprojects list --jsonemits a bare array, so a wrong-host/degraded run is indistinguishable from a small account to a script. Consider{repos, notes}or a nonzero exit when a host contributed zero rows plus a note.Repo.Hostis a token, not a hostname — the clone tree (Dir/github/<owner>/<name>) persists across a[github].hostflip; a stale clone whose origin read fails can collide with a new-host clone at the same dest with no operator tooling to resolve it. Stamping the FQDN and deriving the badge for display would fix identity durability.NewGitearenders an invalid host with%q— the one config-value render on a rejection path; make it categorical likegithubauth.ResolveHost.os.UserConfigDir()honorsXDG_CONFIG_HOME, so on Linux the config path (and thus the pinned host) is env-steerable; the token scrub is the bound that covers it, worth a line in the threat-model comment.Aside
projects list --host github|giteafilter flag could rename to--sourcenow that "host" also means a real hostname; low priority.TestRunDocsServe_*shutdown assertions flake under parallel load (5s timeout; reproducible on main with-count=2), unrelated to fix(forgectl): the GitHub source is pinned to github.com, so a second deployment queries the wrong forge and reports a plausible-looking wrong answer #412 — noting here so the next red CI run has a name.