fix(forge): fail closed on aliases colliding on a routable host

Codex review (PR #520, P1): egress_forge_routes deduplicated referenced
forge accounts by hostname and silently dropped every account after the
first. Two aliases with different token_secret on the same host would let
all API calls to that host authenticate as whichever alias won the dedup —
acting as the wrong forge account and breaking the per-alias identity
guarantee.

Fail closed at composition (before the bottle is created): when two
referenced aliases resolve to the same host but disagree on
origin/auth/token_secret, resolve_forge_associations raises ManifestError.
Identical url/auth under two aliases still dedups to one route. The proxy
routes by host, so an ambiguous credential cannot be selected safely.

Regression tests cover both the conflicting-tokens (raise) and
identical-config (single route) cases; forge.py stays at 100% coverage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 01:14:47 +00:00
committed by didericis
parent cf15500fb8
commit 91f6cbabdc
3 changed files with 73 additions and 2 deletions
+6 -1
View File
@@ -130,7 +130,12 @@ def egress_forge_routes(
(`/api/v1`): the proxy injects the Gitea `token` scheme using the value of
the host env var named by the account's `token_secret`, resolved at launch
from the host environment — the token never enters the bottle. Aliases that
canonicalize to the same host share a single route (deduplicated)."""
canonicalize to the same host share a single route (deduplicated).
Composition (`resolve_forge_associations`) has already rejected referenced
aliases that share a host but disagree on origin/auth/token_secret, so this
host-dedup only ever collapses genuinely identical credentials — it never
silently discards a distinct one."""
out: list[EgressRoute] = []
seen_hosts: set[str] = set()
for assoc in associations:
+28 -1
View File
@@ -233,7 +233,7 @@ def resolve_forge_associations(
)
by_alias.setdefault(alias, []).append(getattr(entry, "Name", ""))
return tuple(
associations = tuple(
ResolvedForgeAssociation(
account=forge_accounts[alias],
repo_names=tuple(sorted(set(names))),
@@ -241,6 +241,33 @@ def resolve_forge_associations(
for alias, names in sorted(by_alias.items())
)
# The egress proxy routes by host, so two referenced aliases that resolve
# to the same host must agree on the credential and target — otherwise the
# generated plan would authenticate every call to that host as whichever
# alias happened to win, silently acting as the wrong forge account. Fail
# closed here (before the bottle is created) rather than dropping a
# credential at route-synthesis time.
by_host: dict[str, ManifestForgeAccount] = {}
for assoc in associations:
acct = assoc.account
prev = by_host.get(acct.host)
if prev is None:
by_host[acct.host] = acct
continue
if (prev.origin, prev.api_prefix, prev.auth_type, prev.token_secret) != (
acct.origin, acct.api_prefix, acct.auth_type, acct.token_secret
):
raise ManifestError(
f"agent '{agent_name}' forge-accounts '{prev.alias}' and "
f"'{acct.alias}' both resolve to host '{acct.host}' but differ "
f"in origin/auth/token_secret. The egress proxy routes by host, "
f"so the intended credential would be ambiguous — every request "
f"to '{acct.host}' would authenticate as one account. Give the "
f"aliases distinct hosts, or make their url/auth identical."
)
return associations
def render_forge_guidance(
associations: tuple[ResolvedForgeAssociation, ...],