A host named in FORGE_ORIGIN_ALLOW can be mirrored as well as connected: its origin's git trusts it by name, as FORGE_OUTBOUND_TRUSTED_HOSTS does #73

Open
tdobbertin-espo wants to merge 1 commit from tdobbertin-espo/forge:fix/origin-allow-mirrors into main
First-time contributor

FORGE_ORIGIN_ALLOW is how an operator vouches for a forge inside the network: README calls it the hosts "an operator vouches for on a LAN", and discovery lets such a host through. But the mirror's git never read it: outbound git trusts only FORGE_OUTBOUND_TRUSTED_HOSTS and the admin's list (0d7ce61a). So a host named only in FORGE_ORIGIN_ALLOW connects, and its live browse, issues and write-through work (the API client does not vet the host), and then every mirror, sync, fetch and push-through from it fails with "refused: the remote's host is a private address". Nothing on the way says which setting is missing.

Now a host name in FORGE_ORIGIN_ALLOW is trusted for its origin's git as an admin-trusted host is. vettedEnv (src/origins/git-mirror.ts) adds the clone URL's host to the trusted names when the operator allows that URL's host:port, or its bare hostname for every port. The match is operatorAllowed (src/origins/discover.ts), which discovery now uses too, so the two read the setting one way.

What is unchanged:

  • The rule is the trusted-host rule, and nothing new: by name, never by address, and git is pinned to the answer that passed. An address literal in FORGE_ORIGIN_ALLOW still reaches discovery only, as the trusted list refuses an address. A trusted name has no further limit on where it resolves, and neither has this one.
  • host:port covers that port only. Another name at the same address is refused.
  • Migrate, webhooks and the Settings → Origins list do not read FORGE_ORIGIN_ALLOW. The origin row does not say "trusted internal host" for it.
  • With FORGE_ORIGIN_ALLOW unset, nothing changes.

Why this, and not a refusal at Connect

The other fix is to refuse such a host at Connect, with a sentence that names FORGE_OUTBOUND_TRUSTED_HOSTS. That would take away what works today for every operator who set FORGE_ORIGIN_ALLOW (live browse, issues and write-through go through the API, which never vetted the host). It would also ask them to write each host twice, in two variables that mean "I vouch for this host". This change gives FORGE_ORIGIN_ALLOW the meaning README already states, and it reuses the rule 0d7ce61a chose, so there is no second rule to keep in step.

Tests

bun 1.3.13, a scrubbed environment (no agent variables):

  • test/origin-allow-mirror.test.ts (new): 5 pass, 0 fail here. On main 9900e4ba the same file gives 2 pass, 3 fail: the vet of an allowed name, the host:port test and the end-to-end mirror all get "refused: the remote's host is a private address". The two that pass on main are the refusals, which this change keeps. The end-to-end half ran here (this machine has a private address); it skips, and says so, where there is none.
  • With the trusted-host, origin, mirror, push-through, migrate, discovery, following and cross suites, in one bun test --timeout 60000 call: 370 pass, 0 fail across 23 files. test/upstream_follow.test.ts alone: 29 pass, 0 fail.
  • bun scripts/gate.ts and tsc --noEmit: clean.

Seen on the way, not changed here: in one process, test/upstream_follow.test.ts's afterAll leaves the mirror's outbound at {} (no loopback allowance), so a mirror test after it is refused. On main, upstream_follow then origins gives 3 failures, and the set above with upstream_follow in it gives 5 (in origins and web-origins), the same on this branch. Each passes alone.

the 23 files

origin-allow-mirror, trusted-hosts, trusted-hosts-ui, origins, origins-oauth, origins-security-provider, origins-sync, web-origins, mirror-outbound, mirror-out, push-through, migrate, oauth-discovery, mcp-discovery-walk, report-discovery, upstream-follow-web, follow-e2e, following, and the five cross files reserved-names, studio, shards, check-list, hollow (each test/<name>.test.ts).

🤖 Generated with Claude Code

`FORGE_ORIGIN_ALLOW` is how an operator vouches for a forge inside the network: README calls it the hosts "an operator vouches for on a LAN", and discovery lets such a host through. But the mirror's git never read it: outbound git trusts only `FORGE_OUTBOUND_TRUSTED_HOSTS` and the admin's list (0d7ce61a). So a host named only in `FORGE_ORIGIN_ALLOW` connects, and its live browse, issues and write-through work (the API client does not vet the host), and then every mirror, sync, fetch and push-through from it fails with "refused: the remote's host is a private address". Nothing on the way says which setting is missing. Now a host name in `FORGE_ORIGIN_ALLOW` is trusted for its origin's git as an admin-trusted host is. `vettedEnv` (`src/origins/git-mirror.ts`) adds the clone URL's host to the trusted names when the operator allows that URL's `host:port`, or its bare hostname for every port. The match is `operatorAllowed` (`src/origins/discover.ts`), which discovery now uses too, so the two read the setting one way. What is unchanged: - The rule is the trusted-host rule, and nothing new: by name, never by address, and git is pinned to the answer that passed. An address literal in `FORGE_ORIGIN_ALLOW` still reaches discovery only, as the trusted list refuses an address. A trusted name has no further limit on where it resolves, and neither has this one. - `host:port` covers that port only. Another name at the same address is refused. - Migrate, webhooks and the Settings → Origins list do not read `FORGE_ORIGIN_ALLOW`. The origin row does not say "trusted internal host" for it. - With `FORGE_ORIGIN_ALLOW` unset, nothing changes. ### Why this, and not a refusal at Connect The other fix is to refuse such a host at Connect, with a sentence that names `FORGE_OUTBOUND_TRUSTED_HOSTS`. That would take away what works today for every operator who set `FORGE_ORIGIN_ALLOW` (live browse, issues and write-through go through the API, which never vetted the host). It would also ask them to write each host twice, in two variables that mean "I vouch for this host". This change gives `FORGE_ORIGIN_ALLOW` the meaning README already states, and it reuses the rule 0d7ce61a chose, so there is no second rule to keep in step. ### Tests bun 1.3.13, a scrubbed environment (no agent variables): - `test/origin-allow-mirror.test.ts` (new): **5 pass, 0 fail** here. On `main` 9900e4ba the same file gives **2 pass, 3 fail**: the vet of an allowed name, the `host:port` test and the end-to-end mirror all get "refused: the remote's host is a private address". The two that pass on `main` are the refusals, which this change keeps. The end-to-end half ran here (this machine has a private address); it skips, and says so, where there is none. - With the trusted-host, origin, mirror, push-through, migrate, discovery, following and cross suites, in one `bun test --timeout 60000` call: **370 pass, 0 fail across 23 files**. `test/upstream_follow.test.ts` alone: **29 pass, 0 fail**. - `bun scripts/gate.ts` and `tsc --noEmit`: clean. Seen on the way, not changed here: in one process, `test/upstream_follow.test.ts`'s `afterAll` leaves the mirror's outbound at `{}` (no loopback allowance), so a mirror test after it is refused. On `main`, `upstream_follow` then `origins` gives 3 failures, and the set above with `upstream_follow` in it gives 5 (in `origins` and `web-origins`), the same on this branch. Each passes alone. <details><summary>the 23 files</summary> origin-allow-mirror, trusted-hosts, trusted-hosts-ui, origins, origins-oauth, origins-security-provider, origins-sync, web-origins, mirror-outbound, mirror-out, push-through, migrate, oauth-discovery, mcp-discovery-walk, report-discovery, upstream-follow-web, follow-e2e, following, and the five cross files reserved-names, studio, shards, check-list, hollow (each `test/<name>.test.ts`). </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code)
FORGE_ORIGIN_ALLOW is how an operator vouches for a forge inside the
network, and discovery lets such a host through. The mirror's git never
read it: outbound git trusts only FORGE_OUTBOUND_TRUSTED_HOSTS and the
admin's list. So a host named only in FORGE_ORIGIN_ALLOW connected, its
live browse, issues and write-through worked (the API client does not
vet the host), and then every mirror, sync, fetch and push-through from
it failed with "refused: the remote's host is a private address".

vettedEnv now adds the clone URL's host to the trusted names when the
operator allows that URL's host:port, or its bare hostname for every
port. operatorAllowed (src/origins/discover.ts) is that match, and
discovery uses it too, so both read the setting one way.

The rule is the trusted-host rule: by name, never by address, and git
is pinned to the answer that passed. An address literal in
FORGE_ORIGIN_ALLOW still reaches discovery only. Migrate, webhooks and
the Settings → Origins list do not read FORGE_ORIGIN_ALLOW. With it
unset, nothing changes.

test/origin-allow-mirror.test.ts: the vet passes, pinned, for an
allowed name and refuses it otherwise; host:port covers one port and
not a neighbour name; an address stays refused; and end to end on this
machine's private address (skipped where it has none), an allowed name
is mirrored over git and the same name not allowed is refused before
any request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Some checks are pending
preview / preview (pull_request) Blocked by required conditions
This pull request has changes conflicting with the target branch.
  • README.md
Some workflows are waiting to be reviewed.
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u fix/origin-allow-mirrors:tdobbertin-espo-fix/origin-allow-mirrors
git switch tdobbertin-espo-fix/origin-allow-mirrors

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff tdobbertin-espo-fix/origin-allow-mirrors
git switch tdobbertin-espo-fix/origin-allow-mirrors
git rebase main
git switch main
git merge --ff-only tdobbertin-espo-fix/origin-allow-mirrors
git switch tdobbertin-espo-fix/origin-allow-mirrors
git rebase main
git switch main
git merge --no-ff tdobbertin-espo-fix/origin-allow-mirrors
git switch main
git merge --squash tdobbertin-espo-fix/origin-allow-mirrors
git switch main
git merge --ff-only tdobbertin-espo-fix/origin-allow-mirrors
git switch main
git merge tdobbertin-espo-fix/origin-allow-mirrors
git push origin main
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
trevato-apps/forge!73
No description provided.