Public: anyone can read this; nobody can change it.

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

opened this view on origin

Check · Review · Land · Live — No review yet. Next: reviewer, no review yet.

Merges on the origin

`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-…

0 files · +0 −0

waiting checks and the preview wait for a writer to approve this head

Check on trevato-apps/forge: running, as of

Checks and the preview wait for a writer to approve this head

A guest

This pull comes from a guest: its preview runs without the app's variables or a forge token until a person who can write here approves it.

This pull cannot land as it is, whatever its review says, because it changes guarded paths: (the diff) (it could not be read).

opened this view on origin

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

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs